手挽手带你学React:四档(下篇)一步一步学会react-redux
手挽手带你学React入门四档,用人话教你react-redux,理解redux架构,以及运用在react中。学完这一章,你就可以开始自己的react项目了。
视频教程
上一篇我们自己实现了Redux,这一篇我们来看看如何去实现一个react-redux
开始之前
本文需要用到的知识
首先你要会React的基础(这是废话)高阶组件
React context
满足这三项我们开始往下看。
react结合redux
搭建基础环境
我们上一章讲过了redux的原理,内部是有一个store的,store只有dispatch才可以控制它变化。还有在文章一开始我们讲了React context 可以跨组件传递数据,那么到现在你想到把我们的store挂到最外层一个组件的context上了吗?话不多说 开始!
我们先修改一下我们的react文件(注意不是redux.html这个文件了)
// App.js import React,{Component} from 'react' import PropTypes from 'prop-types' //引入 export default class App extends Component { constructor(){ super() this.state={ } } componentWillMount(){ // console.log(hashHistory) } render() { return ( <div> <Children /> <ChildrenTwo /> </div> ) } } // 为了展示效果定义子组件一 class Children extends Component{ constructor(){ super() this.state={ } } render(){ return( <div> <h1>我是脑袋</h1> <h2>我是身体</h2> </div> ) } } // 为了展示效果定义子组件二 ChildrenTwo 是 Children的子组件 但是却通过context拿到了App组件拿过来的值 (越级传递) class ChildrenTwo extends Component{ constructor(){ super() this.state={ } } render(){ return( <div> <button>变字</button> <button>变色</button> </div> ) } }
创建基本store
现在我们做好了示例文件的基础模板了,然后我们需要创建一个store.js
// store.js // 这是我们的 reducer const changeDom = (state,action) => { if(!state)return{ text : "我是实例文字", color : 'red' } switch(action.type){ case "CHANGE_TEXT": return{ ...state, text:action.text } case "CHANGE_COLOR": return{ ...state, color:action.color } default: return state } } const creatStore = (reducer)=>{ let state = null const listeners = [] const subscribe = (liestner)=>listeners.push(liestner) const getState = ()=>state const dispatch=(action)=>{ state = reducer(state,action) listeners.map(item=>item()) } dispatch({}) return { getState, dispatch, subscribe } } export {creatStore,changeDom}
结合context使用store
我们现在把我们的子组件通过context公用App的store,并且把渲染方法和dispatch应用进去
// App.js import React,{Component} from 'react' import PropTypes from 'prop-types' //引入 // 引入 store import {changeDom,creatStore} from './store' const store = creatStore(changeDom) export default class App extends Component { // 我们在这里把store挂到context上 static childContextTypes = { store: PropTypes.object } getChildContext(){ return{store} } constructor(){ super() this.state={ } } componentWillMount(){ // console.log(hashHistory) } render() { return ( <div> <Children /> <ChildrenTwo /> </div> ) } } // 这里我们再去修改我们的子孙组建 让他们可以拿到 store // 为了展示效果定义子组件一 class Children extends Component{ static contextTypes = { store: PropTypes.object } constructor(){ super() this.state={ color:"", text:"" } } // 这里我们定义两个渲染方法 getColor(){ let store = this.context.store.getState() this.setState({color:store.color}) } // 这里我们定义两个渲染方法 getText(){ let store = this.context.store.getState() this.setState({text:store.text}) } // 这里组件加载之前渲染 componentWillMount(){ this.getColor() this.getText() } render(){ return( <div> <h1 style={{color:this.state.color}}>{this.state.text}</h1> </div> ) } } // 为了展示效果定义子组件二 ChildrenTwo 是 Children的子组件 但是却通过context拿到了App组件拿过来的值 (越级传递) class ChildrenTwo extends Component{ static contextTypes = { store: PropTypes.object } constructor(){ super() this.state={ } } // 这里我们定义 两个 action changeColor=()=>{ this.context.store.dispatch({type:"CHANGE_COLOR",color:"green"}) console.log(this.context.store.getState()) } changeText=()=>{ this.context.store.dispatch({type:"CHANGE_TEXT",text:"我变了"}) console.log(this.context.store.getState()) //这里方便大家看到数据变了 } render(){ return( <div> <button onClick={()=>{this.changeText()}}>变字</button> <button onClick={()=>{this.changeColor()}}>变色</button> </div> ) } }
显然 现在我们点击按钮的时候,并没有发生任何事情,可是我们看console可以看到,store的数据确实变化了,这是为什么呢?很简单 我们没有把dom监听放进来,下一步我们要设置监听。
设置dom监听
// Children组件 我们在componentWillMount 中添加监听 class Children extends Component{ static contextTypes = { store: PropTypes.object } constructor(){ super() this.state={ color:"", text:"" } } // 这里我们定义两个渲染方法 getColor(){ let store = this.context.store.getState() this.setState({color:store.color}) } // 这里我们定义两个渲染方法 getText(){ let store = this.context.store.getState() this.setState({text:store.text}) } // 这里组件加载之前渲染 componentWillMount(){ this.getColor() this.getText() this.context.store.subscribe(()=>{this.getColor()}) this.context.store.subscribe(()=>{this.getText()})// 使用箭头函数 保证this指向 } render(){ return( <div> <h1 style={{color:this.state.color}}>{this.state.text}</h1> </div> ) } }
到这里我们已经简单地实现了react-redux,可是大家有没有觉得怪怪的?
开始创建connect
没错 到这里我们频繁使用context,并且每个组件都要去手动挂context 这是相当麻烦的,现在我们想要让这些东西都自动包裹 自动生成,我们再怎么做呢。
我们创建一个高阶组件就可以搞定这个问题了(一个函数,接收一个组件,生成另外一个包装好的新组件)
import React, { Component } from 'react' import PropTypes from 'prop-types' export const connect = (WrappedComponent)=>{ class Connect extends Component{ static contextTypes = { store: PropTypes.object } render(){ return<WrappedComponent /> } } return Connect }
我们这里通过 connect函数来接收一个组件 经过一层封装 返回一个包装了context的组件 但是现在这样的话 我们每次使用都还需要大量的书写 this.context 是不是相当恶心呢? 并且每个组件都拿到了store 有很多事它并不需要的,这时候我们就需要告诉这个高阶组件,我这里就只需要这些东西。这就是 mapStateToProps
import React, { Component } from 'react' import PropTypes from 'prop-types' // 示例 这里不用 // const mapStateToProps=(state)=>{ // return{ // color:state.color, // text:state.text, // } // } 就是我们需要从store拿出来的东西 const connect= (mapStateToProps) => (WrappedComponent)=>{ class Connect extends Component{ static contextTypes = { store: PropTypes.object } render(){ const store = this.context.stote let stateProps = mapStateToProps(store.getState()) 然后我们把stateProps用...展开 return<WrappedComponent {...stateProps}/> } } return Connect }
现在我们利用Connect改造我们的组件
// App.js import React,{Component} from 'react' import PropTypes from 'prop-types' //引入 // 引入 store import {changeDom,creatStore} from './store' const store = creatStore(changeDom) // ________________________________把connect_____________________________写在app.js 为的是不引入了 实际上它是单独拆分在react-redux中的 const connect = (mapStateToProps) => (WrappedComponent)=>{ class Connect extends Component{ static contextTypes = { store: PropTypes.object } render(){ const store = this.context.store console.log(store) let stateProps = mapStateToProps(store.getState()) // 然后我们把stateProps用...展开 return<WrappedComponent {...stateProps}/> } } return Connect } // ________________________________把connect_____________________________写在app.js 为的是不引入了 实际上它是单独拆分在react-redux中的 // app.js 的其他内容... // ________________________________对组件一进行修改_____________________________ class Children extends Component{ constructor(){ super() this.state={ color:"", text:"" } } render(){ return( <div> <h1 style={{color:this.props.color}}>{this.props.text}</h1> </div> ) } } const mapStateToProps = (state) => { return { color: state.color, text:state.text } } Children = connect(mapStateToProps)(Children) // ________________________________对组件一进行修改_____________________________
现在我们成功把store里面的所有东西都挂到了props上面,我们不需要去依赖context,只需要调用props即可。剩下的就是我们的数据变更,渲染还有监听了!
对 connect 进一步改造 其实就是像我们上面的组件一样 挂载监听
const connect = (mapStateToProps) => (WrappedComponent)=>{ class Connect extends Component{ static contextTypes = { store: PropTypes.object } componentWillMount(){ const{store} = this.context this.updata() store.subscribe(()=>this.updata()) } updata=()=>{ const { store } = this.context let stateProps = mapStateToProps(store.getState(), this.props) this.setState({ allProps: { // 整合普通的 props 和从 state 生成的 props ...stateProps, ...this.props } }) } render(){ // 然后我们把allProps用...展开 return<WrappedComponent {...this.state.allProps}/> } } return Connect }
mapDispatchToProps
state我们改造完了,dispatch 是不是也要一起改造呢?答案是肯定的,mapDispatchToProps就是做这个的。
我们看看 mapDispatchToProps 是怎么写的 我们倒着推
//const mapDispatchToProps = (dispatch) => { // 传入的是 dispatch // return { //返回一个函数 // changeColor: (color) => { // dispatch({ type: 'CHANGE_COLOR', color: color }) // } // } //} const connect = (mapStateToProps,mapDispatchToProps) => (WrappedComponent)=>{ class Connect extends Component{ static contextTypes = { store: PropTypes.object } componentWillMount(){ const{store} = this.context this.updata() console.log(store) store.subscribe(()=>this.updata()) } updata=()=>{ const { store } = this.context let stateProps = mapStateToProps?mapStateToProps(store.getState(), this.props):{} let dispatchProps = mapDispatchToProps?mapDispatchToProps(store.dispatch,this.props):{} // 我们要考虑到空值处理 this.setState({ allProps: { // 整合普通的 props 和从 state 生成的 props ...stateProps, ...this.props ...dispatchProps, } }) } render(){ // 然后我们把allProps用...展开 return<WrappedComponent {...this.state.allProps}/> } } return Connect }
到这里我们可以把ChildrenTwo的代码也改造了
class ChildrenTwo extends Component{ constructor(){ super() this.state={ } } render(){ console.log(this.props) return( <div> <button onClick={()=>{this.props.changeText("我变了")}}>变字</button> <button onClick={()=>{this.props.changeColor("green")}}>变色</button> </div> ) } } const mapDispatchToProps = (dispatch)=>{ return { //返回一个函数 changeColor: (color) => { dispatch({ type: 'CHANGE_COLOR', color: color }) }, changeText:(text)=>{ dispatch({ type: 'CHANGE_TEXT', text: text }) } } } ChildrenTwo = connect(null,mapDispatchToProps)(ChildrenTwo)
到现在 一个简单的 react-redux已经差不多了,但是react-redux里面还有一个<Provider>
但是我们还没有这东西 这是什么呢?实际上它做的事跟我们App这个组件做的事一毛一样,主要就是把store挂到context上。
export class Provider extends Component{ static childContextTypes = { store: PropTypes.object } getChildContext(){ return{store} } render() { return ( <div>{this.props.children}</div> ) } }
于是 我们现在的代码变成了这样
好了 道理讲完了 我们现在开始拆分代码啦。
// store.js export const creatStore = (reducer)=>{ let state = null const listeners = [] const subscribe = (liestner)=>listeners.push(liestner) const getState = ()=>state const dispatch=(action)=>{ state = reducer(state,action) listeners.map(item=>item()) } dispatch({}) return { getState, dispatch, subscribe } }
// reducer.js // 这是我们的 reducer 可以单独拆分成一个js文件 自己拆吧 export const changeDom = (state,action) => { if(!state)return{ text : "我是实例文字", color : 'red' } switch(action.type){ case "CHANGE_TEXT": return{ ...state, text:action.text } case "CHANGE_COLOR": return{ ...state, color:action.color } default: return state } }
// react-redux.js import React,{Component} from 'react' import PropTypes from 'prop-types' //引入 export const connect = (mapStateToProps,mapDispatchToProps) => (WrappedComponent)=>{ class Connect extends Component{ static contextTypes = { store: PropTypes.object } componentWillMount(){ const{store} = this.context this.updata() store.subscribe(()=>this.updata()) } updata=()=>{ const { store } = this.context let stateProps = mapStateToProps?mapStateToProps(store.getState(), this.props):{} let dispatchProps = mapDispatchToProps?mapDispatchToProps(store.dispatch,this.props):{} // 做一下空值处理 this.setState({ allProps: { // 整合普通的 props 和从 state 生成的 props ...stateProps, ...this.props, ...dispatchProps, } }) } render(){ // 然后我们把allProps用...展开 return<WrappedComponent {...this.state.allProps}/> } } return Connect } export class Provider extends Component{ static childContextTypes = { store: PropTypes.object } getChildContext(){ return {store:this.props.store} } render() { return ( <div>{this.props.children}</div> ) } }
// App.js import React,{Component} from 'react' import Children from './Children' import ChildrenTwo from './ChildrenTwo' export default class App extends Component { constructor(){ super() this.state={ } } render() { return ( <div> <Children /> <ChildrenTwo /> </div> ) } }
// Children.js import React,{Component} from 'react' import{connect} from './react-redux.js' class Children extends Component{ constructor(){ super() this.state={ color:"", text:"" } } render(){ return( <div> <h1 style={{color:this.props.color}}>{this.props.text}</h1> </div> ) } } const mapStateToProps = (state) => { return { color: state.color, text:state.text } } Children = connect(mapStateToProps)(Children) export default Children
// ChildrenTwo.js import React,{Component} from 'react' import{connect} from './react-redux.js' class ChildrenTwo extends Component{ constructor(){ super() this.state={ } } render(){ return( <div> <button onClick={()=>{this.props.changeText("我变了")}}>变字</button> <button onClick={()=>{this.props.changeColor("green")}}>变色</button> </div> ) } } const mapDispatchToProps = (dispatch)=>{ return { //返回一个函数 changeColor: (color) => { dispatch({ type: 'CHANGE_COLOR', color: color }) }, changeText:(text)=>{ dispatch({ type: 'CHANGE_TEXT', text: text }) } } } ChildrenTwo = connect(null,mapDispatchToProps)(ChildrenTwo) export default ChildrenTwo
拆完了的代码是不是简单明了
真正工作里面 我们需要多做两步
npm i redux --save npm i react-redux --save
然后 我们
对照着修改这几个位置
// creatStore 在 redux 插件中 // connect,Provider 在 react-redux 插件中 // 也就是用到哪里了 对应修改哪里 改完了你就发现了新大陆了 import { createStore } from 'redux' import { connect,Provider } from 'react-redux'
不知不觉发现自己不仅仅会用了react-redux 并且还自己实现了一个react-redux 很舒坦的呢
总结
在我们四档下篇到这里结束了,这就是react-redux的实现和写法,大家自己去实现真正的写法,这里不做演示相当于给大家留个作业,有错误或者是有建议 或者有不懂的地方 扫码加我微信给大家解答。