Redux 和React 结合
当Redux?和React?相接合,就是使用Redux进行状态管理,使用React?开发页面UI。相比传统的html,?使用React 开发页面,确实带来了很多好处,组件化,代码复用,但是和Redux?接合时,组件化却也带来了一定的问题,组件层层嵌套,有成千上百个,而store确只有一个,组件中怎么才能获取到store?? 页面UI就是显示应用程序状态的,如果获取不到store中的state,?那就没法渲染内容了。还有一个问题,就是如果状态发生了变化,组件怎么做到实时监听,实时显示最新的状态? 对于第一个问题,React组件中怎么获取到store,你可能想到了,?在整个应用程序的最外层组件中把store?作为props?层层向下传递,对于一个小程序,还可以接受,?但对于一个大型程序呢,不可能成千上百个组件中都写上store?属性吧。还有一个解决方案就是context,? 把所有组件包含在一个context中,context?提供store?属性,这样就不用层层传递,且所有的组件都会获取到store.,方案可以一试 对于第二个问题,组件内部想要实时显示最新的状态,那就要使用store.subscribe()?方法,在其里面注册监听函数,获取最新状态,然后注入到组件中,组件更新的方法,就是调用setState()?方法,那我们的每一个组件都变成了有状态的组件。那store.subsribe()?方法,什么时候注册监听函数,必须组件加载完就要注册,componentDidMounted?里调用store.subscribe(). 根据以上两点分析,尝试写一下代码,看不能能实现Redux和React?的接合,使用create-react-app?创建项目react-redux-demo,然后 cd react-redux-demo && npm i bootstrap redux --save,安装boostrap?和redux。?打开项目,在index.js?中引入boostrap.? import React from ‘react‘; import ReactDOM from ‘react-dom‘; import ‘./index.css‘; import App from ‘./App‘; import ‘bootstrap/dist/css/bootstrap.css‘; // 添加bootstrap 样式 ReactDOM.render(<App />,document.getElementById(‘root‘)); 还是最简单的加减counter?开始,点击add?加1,?点击minus?减1,?点击reset?重置。看一下Redux,?由于Redux?就是action,reducer,store,和React?一点关系都没有,所以完全把创建action,创建stroe的内容写成单独的文件,只暴露出React需要的东西给它调用就好了。React?需要store,?需要action,?因为它要dispatch?action来改变状态。简单起见,把redux的有关内容都放到一个文件中,在src目录下新建一个文件redux.js import {createStore} from ‘redux‘; 现在就要写React,创建页面ui,?先不管交互,先把页面三个按钮和状态的显示画出来,在src下创建一个ThreeButton.js,? import React,{ Component } from ‘react‘ export default class ThreeButton extends Component { render() { return ( <div style={{textAlign: "center"}}> <h1 id="counter">0</h1> <button type="button" className="btn btn-primary" style={{marginRight: ‘10px‘}}>Add</button> <button type="button" className="btn btn-success" style={{marginRight: ‘10px‘}}>Minus</button> <button type="button" className="btn btn-danger">Reset</button> </div> ) } } 然后在App.js中引入 import React from ‘react‘; import ThreeButton from ‘./ThreeButton‘; function App() { return ( <ThreeButton></ThreeButton> ); } export default App; ?准备实现React和Redux的接合,实现页面的交互。首先就是要把store?注入到React中,使用React?的context?api. context使用的最开始,是使用createContext创建一个context,? 在src?目录下新建一个storeContext.js import React from ‘react‘;
const storeContext = React.createContext({store: {}}) export {storeContext}; storeContext?有一个属性Provider,?它是一个组件,有一个value属性,提供真正的组件共享数据,这里就是Redux?创建的store?了。然后用Provider?把组件包起来,该组件和它的子组件都能够获取到共享数据,那就把App?包起来,?那就在index.js中把Redux的store和storeContext.js?引入 import React from ‘react‘; import ReactDOM from ‘react-dom‘; import ‘./index.css‘; import App from ‘./App‘; import ‘bootstrap/dist/css/bootstrap.css‘; // 添加bootstrap 样式 import { store } from ‘./redux‘; // 引入 store
import { storeContext } from ‘./storeContext‘; //?引入storeContext
那ThreeButton.js?就可以获取到store,?那具体是怎么获取到store的呢?首先还是引入storeContext,?然后在类中加一个静态属性contextType,?它赋值为storeContext,?然后组件中就可以使用this.context?获取到store?了。 import React,{ Component } from ‘react‘; import { storeContext } from ‘./storeContext‘; // 引入storeContext export default class ThreeButton extends Component { static contextType = storeContext; // 加静态属性contextType,赋值为storeContext componentDidMount() { let {store} = this.context; // this.context 获取到store console.log(store); } render() { return ( <div style={{textAlign: "center"}}> <h1 id="counter">0</h1> <button type="button" className="btn btn-primary" style={{marginRight: ‘10px‘}}>Add</button> <button type="button" className="btn btn-success" style={{marginRight: ‘10px‘}}>Minus</button> <button type="button" className="btn btn-danger">Reset</button> </div> ) } } 可以看到控制台打印出了store.?组件终于获取到store,?那就要从store中获取state,注入组件中,那组件就要声明一个状态 allState?来接收store中的state,?同时在componentDidMounted?的时候,调用setState?给它赋值 static contextType = storeContext; // 加静态属性contextType,赋值为storeContext state = { allState: {} } componentDidMount() { let {store} = this.context; // this.context 获取到store this.setState({ allState: store.getState() }) } 把h1?中0?改为从状态获取 <h1 id="counter">{this.state.allState.counter}</h1> 页面中显示为5,没有问题,表明从store中获取的状态没有问题。那就要给三个按钮添加click?事件了,dispatch?action?来改变状态,那就添加三个函数。首先从redux.js中引入三个action,?然后声明三个函数dipatch action,?最后就是给按钮添加上click?事件。 import { add,reset } from ‘./redux‘; ..... add = () => { let {store} = this.context; store.dispatch(add); } minus = () => { let {store} = this.context; store.dispatch(minus); } reset = () => { let {store} = this.context; store.dispatch(reset); } ... <button type="button" className="btn btn-primary" style={{marginRight: ‘10px‘}} onClick={this.add}>Add</button> <button type="button" className="btn btn-success" style={{marginRight: ‘10px‘}} onClick={this.minus}>Minus</button> <button type="button" className="btn btn-danger" onClick={this.reset}>Reset</button> 点击了按钮,页面的状态并没有刷新,那就是没有subscribe?监听状态的改变,?还是在componentDidMounted?页面里面调用store.subscribe,它的回调函数也很简单,就是获取状态,调用setState() componentDidMount() { let {store} = this.context; // this.context 获取到store this.setState({ allState: store.getState() }) store.subscribe(() => { this.setState({ allState: store.getState() }) }) } 至此,react?和redux?算是接合成功了。整个threeButton.js?如下 import React,{ Component } from ‘react‘; import { storeContext } from ‘./storeContext‘; // 引入storeContext import { add,reset } from ‘./redux‘; export default class ThreeButton extends Component { static contextType = storeContext; // 加静态属性contextType,赋值为storeContext state = { allState: {} } componentDidMount() { let {store} = this.context; // this.context 获取到store this.setState({ allState: store.getState() }) store.subscribe(() => { this.setState({ allState: store.getState() }) }) } add = () => { let {store} = this.context; store.dispatch(add); } minus = () => { let {store} = this.context; store.dispatch(minus); } reset = () => { let {store} = this.context; store.dispatch(reset); } render() { return ( <div style={{textAlign: "center"}}> <h1 id="counter">{this.state.allState.counter}</h1> <button type="button" className="btn btn-primary" style={{marginRight: ‘10px‘}} onClick={this.add}>Add</button> <button type="button" className="btn btn-success" style={{marginRight: ‘10px‘}} onClick={this.minus}>Minus</button> <button type="button" className="btn btn-danger" onClick={this.reset}>Reset</button> </div> ) } } 现在回想一下组件中获取store,dipatch aciton,和实现实时监听的步骤,?你会发现当我们再创建另外一个组件的时候,它也有好多相同的步骤 , 1 ,添加静态属性contextType,使我们整个组件都能够获取到store,? 肯定相同 2,?添加state来接受store中的state,?肯定相同。 3,componentDidMounted?下获取state ,监听state,?肯定相同 4, dispatch?action,?这个几乎不相同,因为每一个组件触发的action?不同 5,render state,?就是页面的ui,?这个也几乎不同。 我们可以把这个组件分为三个部分,相同的部分不动,那不同的部分要怎么处理?对于不同的部分,通常都是使用函数,不同的部分通过传参的形式传递进来,那就要写一个函数,返回这个组件。由于action?和ui?是两个不同类型的东西,可以分为两种不同的参数,那这个函数接受action 返加一个函数,返加函数再接受一个ui?组件,再返回一个组件,这个组件包含相同的部分。相当于 function connect(action) { return function(Componnet) { return class extends React.componnet { // 相同的部分 render() { return <Componnet {...this.state}></Componnet> } } } } 只要把这个函数封装起来,以后直接调用这个函数,就实现了组件自动获取到store,?自动监听变化,我们只要写ui?和action,然后传递进去就可以了。这个函数其实第三方组件已经封装好了,那就是react-redux?库,它提供了一个connect方法,看一下它的api,?最常用的就是下面的方式 connect(mapStateToProps,mapDispatchToProps)(MyComponent)。 和我们自己写的connect?函数使用方法一致,?只不过它的第一个函数可以接受更多的参数,mapStateToProps,?把state?转化成props,因为connect?返回的组件中能够获取到store中的state,?它要把state?传递给myComponnet,?因为myCompoent?才是负责渲染ui,?对于myComponnet?来说,它就是props.?同理也适用于mapDispatchToProps,?在connect的组件它是能获取到store中的dispatch,? 当传递给myComponent的时候,它就变成了Props.? 再看一下这两个参数怎么使用,首先它们是函数,然后返回对象。?为什么要这样设计呢?只有函数,才能调用,才能通过参数把state和disptch? 进行注入,返回对象,便于对象的合并,把所有对象进行合并,形成props?传递给myComponnet. 对于mapStateToProps?来说,它接受一个state作为参数,返回一个对象,这个对象中的属性就可以在myComponet中使用props?进行获取并使用,值呢?就是?参数state中的属性,myComponent?组件要用到state中的哪个属性,就读取state中的哪个属性作为参数。 const mapStateToProps = state => ({ counter: state.counter }) mapDispatchToProps,则相对麻烦一点,它接受一个dispatch?作为参数,返回的对象中属性也是可以在myComponet中使用props?进行获取并使用,值呢,是一个函数,参数可以接受也可以不接受,函数体则是dispatch?action const mapDispatchToProps = dispatch => ({ add: () => dispatch({type: ‘ADD‘}) }) React-Redux?除了connect?函数外,还提供了Provider?组件,和我们自定义的storeContext.Provider?一致,不过它的使用方式是直接提供属性,组件身上的属性都能被子组件获取到。npm i react-redux --save?使用react-redux?重写组件。 首先把storeContext.js?文件去掉,然后在index.js中从React-Redux引入Provider?组件,包含App import React from ‘react‘; import ReactDOM from ‘react-dom‘; import ‘./index.css‘; import App from ‘./App‘; import ‘bootstrap/dist/css/bootstrap.css‘; // 添加bootstrap 样式 import { store } from ‘./redux‘; // 引入 store import { Provider } from ‘react-redux‘; // 提供sotre 作为共享数据,App及其子组件都能获取到store const ProviderWrapper = <Provider store={store}> <App /> </Provider> ReactDOM.render(ProviderWrapper,document.getElementById(‘root‘)); 然后ThreeButton.js?就要分为两部分了,一个部分是connect?函数中的第一部分connect(mapStateToProps,mapDispatchToProps),?主要的作用就是把store?获取,转化为props. 另一部分是connect的第二部分myComponent,? 它呢,就是接受到props,?渲染组件。?在一个文件中也可以,分两个文件也没有问题。我们就在一个文件中写了, import React from ‘react‘; import { add,reset } from ‘./redux‘;
import { connect } from ‘react-redux‘;
// 纯渲染组件 function ThreeButton(props) { return ( <div style={{textAlign: "center"}}> <h1 id="counter">{props.counter}</h1> <button type="button" className="btn btn-primary" style={{marginRight: ‘10px‘}} onClick={props.add}>Add</button> <button type="button" className="btn btn-success" style={{marginRight: ‘10px‘}} onClick={props.minus}>Minus</button> <button type="button" className="btn btn-danger" onClick={props.reset}>Reset</button> </div> ) } // 把store中的state 转化为纯渲染组件props const mapStateToProps = state => ({ counter: state.counter }) // 获取store中的dispatch,同时和action接合,组成纯渲染组件props,渲染组件中,直接调用对象的属性,就可以dispatch action 了 const mapDispatchToProps = dispatch => ({ add: () => dispatch(add),minus: () => dispatch(minus),reset: () => dispatch(reset) }) // connect 函数把它们接合起来,ThreeButton就可以使用props来使用mapStateToProps和mapDispatchToProps中返回的对象属性 // 同时返回一个组件,可以在父组件App.js 中直接调用 export default connect( mapStateToProps,mapDispatchToProps,)(ThreeButton) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |