js创建虚拟dom和jsx创建虚拟dom
-
js创建虚拟dom
<!-- 准备好一个“容器” --> <div id="test"></div> <!-- 引入react核心库 --> <script type="text/javascript" src="../js/react.development.js"></script> <!-- 引入react-dom,用于支持react操作DOM --> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" > //1.创建虚拟DOM const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React')) //2.渲染虚拟DOM到页面 ReactDOM.render(VDOM,document.getElementById('test')) </script>
js创建虚拟dom,如果有嵌套结构比较繁琐!
-
jsx创建虚拟dom
<!-- 准备好一个“容器” --> <div id="test"></div> <!-- 引入react核心库 --> <script type="text/javascript" src="../js/react.development.js"></script> <!-- 引入react-dom,用于支持react操作DOM --> <script type="text/javascript" src="../js/react-dom.development.js"></script> <!-- 引入babel,用于将jsx转为js --> <script type="text/javascript" src="../js/babel.min.js"></script> <script type="text/babel" > /* 此处一定要写babel */ //1.创建虚拟DOM const VDOM = ( /* 此处一定不要写引号,因为不是字符串 */ <h1 id="title"> <span>Hello,React</span> </h1> ) //2.渲染虚拟DOM到页面 ReactDOM.render(VDOM,document.getElementById('test')) </script>
jsx最后会被babel编译成js代码的
-
虚拟dom和真实dom的比较
<script type="text/babel" > /* 此处一定要写babel */ //1.创建虚拟DOM const VDOM = ( /* 此处一定不要写引号,因为不是字符串 */ <h1 id="title"> <span>Hello,React</span> </h1> ) //2.渲染虚拟DOM到页面 ReactDOM.render(VDOM,document.getElementById('test')) const TDOM = document.getElementById('demo') console.log('虚拟DOM',VDOM); console.log('真实DOM',TDOM); debugger; // console.log(typeof VDOM); // console.log(VDOM instanceof Object); /* 关于虚拟DOM: 1.本质是Object类型的对象(一般对象) 2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。 3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。 */ </script>
虚拟dom上的属性比较少
关于jsx
-
jsx中标签代码中,如果要写js代码,要加上{},而且{}里面只能写一个表达式
- {}中的语句可以给变量赋值就是表达式,如arr.map(), function test () {}
-
jsx标签代码中,如果写数组,会直接被遍历
-
更多
/*
jsx语法规则:
1.定义虚拟DOM时,不要写引号。
2.标签中混入JS表达式时要用{}。
3.样式的类名指定不要用class,要用className。
4.内联样式,要用style={{key:value}}的形式去写。
5.只有一个根标签
6.标签必须闭合
7.标签首字母
(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
*/
函数式组件和类式组件
-
函数式组件
-
函数式组件,因为是window调用的,而babel中开启了严格模式,所以这里的this是undefined!
-
<script type="text/babel"> //1.创建函数式组件 function MyComponent(){ console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式 return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2> } //2.渲染组件到页面 ReactDOM.render(<MyComponent/>,document.getElementById('test')) /* 执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么? 1.React解析组件标签,找到了MyComponent组件。 2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。 */ </script>
-
-
类式组件
- 是一个继承自React.Component的类
- 要重写父类的render方法
- 其state和props在实例中
- 方法中的this是由调用者决定的,render中的this指向该实例
- render方法会在初始化的时候调用,和更新后调用
- 通过this.setState({})可以更新页面上的组件,setState()方法是上级类上的
组件中的三大属性
类式组件
state
-
初始化state
- 在构造函数中初始化state
- this.state = {isHot:false,wind:‘微风’}
- 在构造函数中初始化state
-
获取state中的值
- 在render中的this指向是指向该实例的
- 如果onClick事件中的回调函数里是原型对象的方法,并在里通过this.state获取状态,就会因this指向而报错,因为这个方法不是组件对象调用的,而是window,所以this指向的是undefined
-
对state进行操作
- 不能直接进行操作!
- 要用this.setState()操作,而且this.setState()是会合并之前的state中的属性的,而不是进行的替换操作
-
解决this指向问题
-
在构造函数中 this.changeWeather = this.changeWeather.bind(this),会在当前实例上添加类中改变this指向后的函数
-
给实例添加箭头函数方法
-
<script type="text/babel"> //1.创建组件 class Weather extends React.Component{ //初始化状态 //实例上的状态属性 state = {isHot:false,wind:'微风'} render(){ const {isHot,wind} = this.state return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1> } //自定义方法————要用赋值语句的形式+箭头函数 changeWeather = ()=>{ const isHot = this.state.isHot this.setState({isHot:!isHot}) } } //2.渲染组件到页面 ReactDOM.render(<Weather/>,document.getElementById('test')) </script>
-
-
props
-
传递props属性,ReactDOM.render(<Person {…p}/>,document.getElementById(‘test3’))
-
获取props,this.props
-
对props进行限制
-
对组件的props的进行限制和设置默认Props属性,只能写在该实例的原型对象中
-
//第一中写法 //对标签属性进行类型、必要性的限制 Person.propTypes = { name:PropTypes.string.isRequired, //限制name必传,且为字符串 sex:PropTypes.string,//限制sex为字符串 age:PropTypes.number,//限制age为数值 speak:PropTypes.func,//限制speak为函数 } //指定默认标签属性值 Person.defaultProps = { sex:'男',//sex默认值为男 age:18 //age默认值为18 } //第二中写法 //加上static就是给原型对象上添加属性和方法 //对标签属性进行类型、必要性的限制 static propTypes = { name:PropTypes.string.isRequired, //限制name必传,且为字符串 sex:PropTypes.string,//限制sex为字符串 age:PropTypes.number,//限制age为数值 } //指定默认标签属性值 static defaultProps = { sex:'男',//sex默认值为男 age:18 //age默认值为18 }
-
-
ref(dom节点对象)
-
字符串形式的ref
- 设置ref:ref=“input2”
- 获取ref:this.refs
- 不常用
-
回调函数设置ref
-
//第一种方式,会被重复调用 //设置ref <input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/> //第二种方式,不会被重复调用 /* saveInput = (c)=>{ this.input1 = c; console.log('@',c); } */ //<input ref={this.saveInput} type="text"/><br/><br/>
-
-
createRef设置ref
-
//<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/> //myRef = React.createRef() /* showData = ()=>{ alert(this.myRef.current.value); } */
-
函数组件
函数组件只有props属性,但是可以使用hooks添加state等属性…
限制props和设置默认的props
//创建组件
function Person (props){
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
react中的事件处理
/*
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
(2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
*/
高阶函数和函数的柯里化
//#region
/*
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
*/
//#endregion
收集表单数据
非受控组件,现用现取(使用ref获取input的值)
受控组件(将Input值放入state中)
实现方式:
使用函数的柯里化
//<input onChange={this.saveFormData('username')} type="text" name="username"/>
/*
//保存表单数据到状态中
saveFormData = (dataType)=>{
return (event)=>{
this.setState({[dataType]:event.target.value})
}
}
*/
不使用函数的柯里化实现
//<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/>
/*
//保存表单数据到状态中
saveFormData = (dataType,event)=>{
this.setState({[dataType]:event.target.value})
}
*/
关于Diff算法
如果给标签加上key就会使用到diff算法
diff算法会将新的虚拟dom和旧虚拟dom进行比较生成真实的dom
1. 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
2. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
3. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。
Es6扩展
扩展运算符…
扩展运算符是不能展开对象的属性的,但是可以用{…xx}生成新的对象,[…arr]也是生成新的数组
{…xx,xx:xxx}可以复制对象的属性时,对某个属性进行重新赋值操作
=====================脚手架中使用react
子组件如何如何给父组件传递数据
-
使用回调函数
-
父组件给子组件传递一个函数,子组件调用即可
-
//App.jsx //父组件定义好方法,然后将方法传递给子组件 /* <Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone}/> //checkAllTodo用于全选 checkAllTodo = (done)=>{ //获取原来的todos const {todos} = this.state //加工数据 const newTodos = todos.map((todoObj)=>{ return {...todoObj,done} }) //更新状态 this.setState({todos:newTodos}) } */ //子组件调用 /* <input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total && total !== 0 ? true : false}/> //全选checkbox的回调 handleCheckAll = (event)=>{ this.props.checkAllTodo(event.target.checked) } */
-
-
使用消息订阅与发布
-
这个不是react中的方法
-
可以在任意组件中使用
-
//引入 import PubSub from 'pubsub-js' //消息订阅 /* this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{ this.setState(stateObj) }) */ //消息发布 /* PubSub.publish('atguigu',{isLoading:false,users:response.data.items}) */ //取消消息订阅 /* componentWillUnmount(){ PubSub.unsubscribe(this.token) } */
-
路由的使用
路由的匹配规则:点击了某个路由链接后,url地址会变成改地址,然后history会监听到url地址的变化,然后到App.jsx中匹配路由规则,匹配到了后显示该组件,继续匹配该组件中的的路由规则…,所以这也是路由匹配规则中不能使用exact的原因
-
基本使用
-
路由链接组件比如Link,NavLink等最后会被渲染成a标签
-
App.jsx //引入 import {Link,Route} from 'react-router-dom' {/* 在React中靠路由链接实现切换组件--编写路由链接 */} <Link className="list-group-item" to="/about">About</Link> <Link className="list-group-item" to="/home">Home</Link> {/* 注册路由 */} <Route path="/about" component={About}/> <Route path="/home" component={Home}/> index.js //引入react核心库 import React from 'react' //引入ReactDOM import ReactDOM from 'react-dom' // import {BrowserRouter} from 'react-router-dom' //引入App import App from './App' //用BrowserRouter包裹着<App/>保证路由组件使用同一个Router ReactDOM.render( <BrowserRouter> <App/> </BrowserRouter>, document.getElementById('root') )
-
-
NavLink
-
点击后,默认会给当前的a标签添加active类
-
修改模拟的点击添加的类名,activeClassName=“atguigu”
-
封装NavLink
-
MyNavLink /* <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/> */ App.jsx //<MyNavLink to="/about">About</MyNavLink>
-
-
-
Switch的使用
-
不加Switch组件,匹配到路由后,还会继续往下匹配
-
<Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Route path="/home" component={Test}/> </Switch>
-
-
精准匹配和模糊匹配
- 路由默认是模糊匹配
- Route标签中使用exact可以实现精准匹配
- 精准匹配不能滥用,如果是二级路由的话,使用其就会有问题
-
Redirect
-
如果匹配不到路由,会进行重定向到默认路由
-
<Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch>
-
-
嵌套路由的使用
-
传递参数(params,search,state),包括编程式路由导航
-
Message中的index.jsx import React, { Component } from 'react' import Detail from './Detail' import {Link, Route} from 'react-router-dom' import { countSubscriptions } from 'pubsub-js' export default class Message extends Component { state={ messagedata:[ {id:1,title:'消息1'}, {id:2,title:'消息2'}, {id:3,title:'消息3'} ] } pushShow=(id,title)=>{ // this.props.history.push(`/home/message/detail/${id}/${title}`) // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`) this.props.history.push('/home/message/detail',{id,title}) } replaceShow=(id,title)=>{ // this.props.history.replace(`/home/message/detail/${id}/${title}`) this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`) this.props.history.replace('/home/message/detail',{id,title}) } back=()=>{ this.props.history.goBack() } forward=()=>{ this.props.history.goForward() } go=()=>{ this.props.history.go(-2) } render() { return ( <ul> { this.state.messagedata.map(v=>{ return ( <li key={v.id}> {/* params传递参数 */} {/* <Link to={`/home/message/detail/${v.id}/${v.title}`}>{v.title}</Link> */} {/* search传递参数 */} {/* <Link to={`/home/message/detail?id=${v.id}&title=${v.title}`}>{v.title}</Link> */} {/* state传递参数 */} <Link to={{pathname:'/home/message/detail',state:{id:v.id,title:v.title}}}>{v.title}</Link> <button onClick={()=>{this.pushShow(v.id,v.title)}}>pushShow</button> <button onClick={()=>{this.replaceShow(v.id,v.title)}}>replaceShow</button> </li> ) }) } <br/> <div> {/* prams传递参数 */} {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */} {/* search传递参数 */} {/* <Route path="/home/message/detail" component={Detail}></Route> */} {/* state传递参数 */} <Route path="/home/message/detail" component={Detail}></Route> </div> <br/> <div> <button onClick={this.back}>back</button> <button onClick={this.forward}>forward</button> <button onClick={this.go}>go</button> </div> </ul> ) } } //Message组件中的Detail中的jsx import React, { Component } from 'react' import qs from 'querystring' export default class Detail extends Component { state={ detaildata:[ {id:1,content:'111'}, {id:2,content:'222'}, {id:3,content:'333'} ] } render() { console.log(this.props) // 接受params传递的参数 // let {id,title}=this.props.match.params //接受并处理search传递的参数 // let search=this.props.location.search.slice(1) // let {id,title}=qs.parse(search) //接受state传递的参数 let {id,title}=this.props.location.state let obj=this.state.detaildata.find(v=>{ return v.id===parseInt(id) }) return ( <div> <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{obj.content}</li> </ul> </div> ) } }
-
由于params,search参数都在地址栏上,所以刷新页面肯定不会导致数据的丢失问题
-
state参数是存放在history中的,(BrowserRouter)刷新页面不会导致数据的丢失,(HashRouter)刷新页面会丢失数据
-
-
push与replace模式
- 默认为push模式
- 使用push会在history中推入一个历史记录
- replace不会推入一个历史记录
- 使用replace模式,<MyNavLink replace to="/about">About</MyNavLink>
-
withRouter的使用
- 路由组件中可以获取到histtory的信息,但是无法给它传递属性
- 一般组件没有路由组件中特有的API,比如获取history信息
- withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
- export default withRouter(Header),返回一个新的组件
关于样式丢失的问题
在index.html中引入外部的样式使用./引入的,url地址为嵌套路由的url地址时,可能刷新页面后,导致样式丢失的问题
解决办法:
- 使用/xxx
- 使用%PUBLIC_URL%/xxxx
- 使用HashRouter,因为HashRouter中的url地址中带#,而服务器是不看#后面的地址的
redux和react-redux的使用
redux不是react中的,redux是一种状态管理,可以存取组件中共有的属性,方便管理
三个重要文件:action,store,reducer
注意:redux状态的改变不会导致组件的更新,所以要监听其状态的改变
- 基本使用
index.js
/*
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})
*/
redux文件夹
store.js
/*
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//暴露store
export default createStore(countReducer)
*/
count_reducer.js
/*
const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
// console.log(preState);
//从action对象中获取:type、data
const {type,data} = action
//根据type决定如何加工数据
switch (type) {
case 'increment': //如果是加
return preState + data
case 'decrement': //若果是减
return preState - data
default:
return preState
}
}
*/
Count文件夹下
index.jsx
//import store from '../../redux/store'
//获取store中的state
//<h1>当前求和为:{store.getState()}</h1>
/*
//更新store中的state
//加法
increment = ()=>{
const {value} = this.selectNumber
store.dispatch({type:'increment',data:value*1})
}
*/
-
优化(使用共有变量,将生成action的方法放入到action.js中)
redux文件夹 constant.js /* 该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错 */ export const INCREMENT = 'increment' export const DECREMENT = 'decrement' count_action.js /* 该文件专门为Count组件生成action对象 */ import {INCREMENT,DECREMENT} from './constant' export const createIncrementAction = data => ({type:INCREMENT,data}) export const createDecrementAction = data => ({type:DECREMENT,data}) count_reducer.js /* 1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数 2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action) */ import {INCREMENT,DECREMENT} from './constant' const initState = 0 //初始化状态 export default function countReducer(preState=initState,action){ // console.log(preState); //从action对象中获取:type、data const {type,data} = action //根据type决定如何加工数据 switch (type) { case INCREMENT: //如果是加 return preState + data case DECREMENT: //若果是减 return preState - data default: return preState } } Count文件夹下 index.jsx //加法 increment = ()=>{ const {value} = this.selectNumber store.dispatch(createIncrementAction(value*1)) }
-
如何在异步中改变store中的state
-
在组件中异步代码中去修改store中的state
-
Count文件夹下 index.jsx //异步加 incrementAsync = ()=>{ const {value} = this.selectNumber setTimeout(()=>{ store.dispatch(createIncrementAction(value*1)) },500) }
-
-
使用函数式action
-
Count文件夹下 index.jsx //异步加 incrementAsync = ()=>{ const {value} = this.selectNumber // setTimeout(()=>{ store.dispatch(createIncrementAsyncAction(value*1,500)) // },500) } redux文件夹下 count_action.js //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。 export const createIncrementAsyncAction = (data,time) => { return (dispatch)=>{ setTimeout(()=>{ dispatch(createIncrementAction(data)) },time) } } redux文件夹 store.js //引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' //暴露store export default createStore(countReducer,applyMiddleware(thunk))
-
-
======================================================
上面只是用了redux,每一个组件都能对store的state的进行操作,不方便管理,所以还要使用react-redux
react-redux中只有容器组件可以对sotre中的state进行操作,其ui组件不能,只能靠容器组件传递state属性和修改state的回调函数
-
react-redux基本使用
-
containers文件夹下 //代表容器组件 Count文件夹下 index.jsx //引入Count的UI组件 import CountUI from '../../components/Count' //引入action import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action' //引入connect用于连接UI组件与redux import {connect} from 'react-redux' /* 1.mapStateToProps函数返回的是一个对象; 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value 3.mapStateToProps用于传递状态 */ function mapStateToProps(state){ return {count:state} } /* 1.mapDispatchToProps函数返回的是一个对象; 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value 3.mapDispatchToProps用于传递操作状态的方法 */ function mapDispatchToProps(dispatch){ return { jia:number => dispatch(createIncrementAction(number)), jian:number => dispatch(createDecrementAction(number)), jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)), } } //使用connect()()创建并暴露一个Count的容器组件 export default connect(mapStateToProps,mapDispatchToProps)(CountUI) components文件夹 Count文件夹下 //代表ui组件 index.jsx //获取 <h1>当前求和为:{this.props.count}</h1> //修改 //奇数再加 incrementIfOdd = ()=>{ const {value} = this.selectNumber if(this.props.count % 2 !== 0){ this.props.jia(value*1) } } //异步加 incrementAsync = ()=>{ const {value} = this.selectNumber this.props.jiaAsync(value*1,500) } App.jsx import React, { Component } from 'react' import Count from './containers/Count' import store from './redux/store' export default class App extends Component { render() { return ( <div> {/* 给容器组件传递store */} <Count store={store} /> </div> ) } }
-
上面的store中的reducer只有一个,直接用createStore(countReducer,applyMiddleware(thunk))加入了,这样store中的state就是单个属性,非对象,直接获取就可以了
-
react-redux的优化使用
-
加入了react-redux不需要我们手动去订阅sotre中state改变的信息了,加入Provider可以一次性将store传递给多个容器对象,可以将容器对象和ui对象写在一起,mapDispatchToProps可以是一个对象,然后直接把生成action的方法传递给ui对象,react-redux会自动把ui对象生成的action提交到store中的
-
index.js import React from 'react' import ReactDOM from 'react-dom' import App from './App' import store from './redux/store' import {Provider} from 'react-redux' ReactDOM.render( <Provider store={store}> <App/> </Provider>, document.getElementById('root') ) containers文件夹下 Count文件夹 index.jsx //使用connect()()创建并暴露一个Count的容器组件 export default connect( state => ({count:state}), //mapDispatchToProps的一般写法 /* dispatch => ({ jia:number => dispatch(createIncrementAction(number)), jian:number => dispatch(createDecrementAction(number)), jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)), }) */ //mapDispatchToProps的简写 { jia:createIncrementAction, jian:createDecrementAction, jiaAsync:createIncrementAsyncAction, } )(Count)
-
-
多个容器组件实现数据共享
-
store中的state是一个对象,该对象中的默认属性值是由其对应的reducer提供的
-
containers文件夹 Count/index.jsx //使用connect()()创建并暴露一个Count的容器组件 export default connect( state => ({ count:state.he, renshu:state.rens.length }), { jia:createIncrementAction, jian:createDecrementAction, jiaAsync:createIncrementAsyncAction, } )(Count) Person/index.jsx export default connect( state => ({yiduiren:state.rens,he:state.he}),//映射状态 {jiaYiRen:createAddPersonAction}//映射操作状态的方法 )(Person) redux文件夹 actions/person.js import {ADD_PERSON} from '../constant' //创建增加一个人的action动作对象 export const createAddPersonAction = personObj => ({type:ADD_PERSON,data:personObj}) reducers/person.js import {ADD_PERSON} from '../constant' reducers/person.js //初始化人的列表 const initState = [{id:'001',name:'tom',age:18}] export default function personReducer(preState=initState,action){ // console.log('personReducer@#@#@#'); const {type,data} = action switch (type) { case ADD_PERSON: //若是添加一个人 return [data,...preState] default: return preState } } store.js //引入createStore,专门用于创建redux中最为核心的store对象 import {createStore,applyMiddleware,combineReducers} from 'redux' //引入为Count组件服务的reducer import countReducer from './reducers/count' //引入为Count组件服务的reducer import personReducer from './reducers/person' //引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' //汇总所有的reducer变为一个总的reducer const allReducer = combineReducers({ he:countReducer, rens:personReducer }) //暴露store export default createStore(allReducer,applyMiddleware(thunk)) App.jsx import React, { Component } from 'react' import Count from './containers/Count' import Person from './containers/Person' export default class App extends Component { render() { return ( <div> <Count/> <hr/> <Person/> </div> ) } }
-
注意reducer中的preState是一个数组或者对象的话,就要返回一个新的数组或者对象,因为redux会比较两者的地址,如果地址不变,改变是无效的
-
使用react-redux开发者工具监测store中的状态改变
-
redux/store.js import {composeWithDevTools} from 'redux-devtools-extension' export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
-