Redux该怎么在React中使用呢?
前言 当业务逻辑越来越复杂的时候,你会发现使用React原生的state会是件很繁琐的事情,比如我的上一篇学习记录讲到的跨组件通信案例,管理各个组件的state就像蜘蛛丝一样牵连复杂,并且具有很高的风险,稍微不注意state就会发生预料不到的改变,这个时候我们可以用到强大且干净利落的状态管理工具Redux。
为什么要用到状态管理工具? 前端工程师在团队中肩负的任务越来越繁重,开发SPA单页面应用遇到错综复杂的需求也很常见,这时候就需要管理非常多的state,其中可能包括服务器的相应、缓存数据、本地生成尚未持久化到服务器的数据,UI的各种状态,当一个组件的state变化会影响到其他一个甚至多个组件state变化时,并且当多个路由下的组件需要相互通信时,这个时候仍然使用React自带的state来管理状态显然是一件头疼的事情,因此出现各种状态管理工具,Redux就是其中优秀工具之一。
Redux的三大原则 单一数据流 应用中的state都被存储在唯一的store中,所有数据一目了然,这给我们带来非常大的便利来调试应用,任何组件都可以访问到store的任何数据,这样我们就不需要操心各种跨组件通信,当产品经理改需求时,也不用让整个应用大动干戈,因为你只需要维护唯一的数据源store。
State只能读取 第一大原则中我们说到“任何组件都能访问到store的任何数据”,这是不是表示store就像全局变量一样危险?当然不是,因为store中的state是只读的,如果要修改state,都要严格按照Redux的API来执行,也就是action,action是一个用于描述事件将如何发生的对象,只有action才能改变store中的state,因此Redux让应用的状态改变变得“可预测”。
使用纯函数来修改state 单有action是无法改变state的,这时我们需要配合Reducer纯函数,Reducer存在的意义就是接受action的指令改变state并且返回新的state,它是纯函数,不会对应用产生任何的副作用。
简单demo 说了这么多,我们用code来举个例子🌰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import { createStore } from 'redux' ;function counter (state = 0 , action ) { switch (action.type) { case 'INCREMENT' : return state + 1 ; case 'DECREMENT' : return state - 1 ; default : return state; } } let store = createStore(counter);store.subscribe(() => console .log(store.getState()) ); store.dispatch({ type : 'INCREMENT' }); store.dispatch({ type : 'INCREMENT' }); store.dispatch({ type : 'DECREMENT' });
这个例子涵盖了大部分Redux的API和使用方式,但是具体应用到React当中,我们还需进行一下步骤。
一个简单的React与Redux计数器demo 我们要了解一个事实,React与Redux到底是什么关系?其实这两者一点关系都没有,Redux是一个单独的状态管理工具,它不仅可以用在React中,还可以用到其他的前框架中,只不过因为React的“ state 函数的形式来描述界面”的特性,React与Redux成为一对较好的搭档。也就是为什么我们说Redux是React全家桶之一。
1、 用create-react-app初始化一个react项目,npm install redux --save,下载redux包。
2、创建index.redux.js文件,写入计数器需要的action对象、reducer函数,和action creator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const ADD_NUM = 'add num' ;const REDUCE_NUM ='reduce num' ;export function counter (state=0 ,action ) { switch (action.type){ case ADD_NUM: return state+1 case REDUCE_NUM: return state-1 default : return 10 } } export function addNum ( ) { return { type :ADD_NUM } } export function reduceNum ( ) { return { type : REDUCE_NUM } }
3、修改index.js为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React from "react" ;import ReactDom from "react-dom" ;import { createStore } from 'redux' ;import App from "./App" ;import { counter, addNum, reduceNum } from './index.redux' ;const store = createStore(counter);function render ( ) { ReactDom.render(<App store ={store} addNum ={addNum} reduceNum ={reduceNum} /> ,document.getElementById('root')); } //初次渲染 render(); //利用store API监测当store中的state变化的时候自动启动subscribe监听函数,再次渲染render store.subscribe(render);
4、修改App.js为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import React from 'react' ;class App extends React .Component { render(){ const store = this .props.store; const num = store.getState(); const addNum = this .props.addNum; const reduceNum = this .props.reduceNum; return ( <div> <h1>现在的数量为{num}</h1> <button onClick={() => store.dispatch(addNum())} >+</ button> <button onClick={() => store.dispatch(reduceNum())} >-</button > </div> ) } } export default App
这样我们就完成一个简单的React与Redux结合的计数器例子在线查看我的例子(需翻墙:Codesandbox ,也许你会问这么简单的逻辑为何要用这么复杂的方式来实现,因为这只是简单介绍Redux的用法,当需求超级复杂的时候,使用Redux带来的便利谁用谁知道~
异步Redux的使用方式 你会注意到上个例子中,如果要用异步函数,是否无从下手,这时候就需要用到redux-thunk这个插件来实现异步,同样我们以前一个计数器例子来实现。
1、安装npm install redux-thunk --save插件
2、修改index.js为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import React from "react" ;import ReactDom from "react-dom" ;import { createStore,applyMiddleware } from 'redux' ;import thunk from 'redux-thunk' ;import App from "./App" ;import { counter, addNum, reduceNum, addNumAsync } from './index.redux' ;const store = createStore(counter, applyMiddleware(thunk)); function render ( ) { ReactDom.render(<App store ={store} addNum ={addNum} reduceNum ={reduceNum} addNumAsync ={addNumAsync} /> ,document.getElementById('root')); } //初次渲染 render(); //利用store API监测当store变化的时候自动启动subscribe监听函数 store.subscribe(render);
3、修改index.redux.js为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const ADD_NUM = "add num" ;const REDUCE_NUM = "reduce num" ;export function counter (state = 0 , action ) { switch (action.type) { case ADD_NUM: return state + 1 ; case REDUCE_NUM: return state - 1 ; default : return 10 ; } } export function addNum ( ) { return { type : ADD_NUM }; } export function reduceNum ( ) { return { type : REDUCE_NUM }; } export function addNumAsync ( ) { return dispatch => { setTimeout(() => { dispatch(addNum()); }, 2000 ); }; }
修改App.js为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import React from 'react' ;class App extends React .Component { render(){ const store = this .props.store; const num = store.getState(); const addNum = this .props.addNum; const reduceNum = this .props.reduceNum; const addNumAsync = this .props.addNumAsync; return ( <div> <h1>现在的数量为{num}</h1> <button onClick={() => store.dispatch(addNum())} >+</ button> <button onClick={() => store.dispatch(reduceNum())} >-</button > <button onClick={() => store.dispatch(addNumAsync())} >+ Async</button> </ div> ) } } export default App
使用异步时,我们只需要在applyMiddleware中加一个thunk即可实现,查看在线demo:Codesandbox
为React量身定制 的React-Redux 我们之前说过React与Redux并没有什么直接的关系,但是React-Redux就不一样了,React-Redux让React使用Redux变得更加简洁,可以理解为React-Redux是Redux专门为React定制的状态管理工具。
1、安装react-redux,npm install react-redux --save
2、修改index.js为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React from "react" ;import ReactDom from "react-dom" ;import { createStore,applyMiddleware } from 'redux' ;import thunk from 'redux-thunk' ;import { Provider } from 'react-redux' ;import App from "./App" ; import { counter } from './index.redux' ;const store = createStore(counter, applyMiddleware(thunk));ReactDom.render( ( <Provider store={store} > <App /> </Provider> ), document.getElementById('root') ) / / 以下是未使用React-redux / /function render() { / /将redux组件传入App组件当中 / / ReactDom.render(<App store={store} addNum={addNum} reduceNum={reduceNum} addNumAsync={addNumAsync} / >,document .getElementById('root' ));
3、修改App.js为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import React from 'react' ;import { connect } from 'react-redux' ;import { addNum, reduceNum, addNumAsync } from './index.redux' ;class App extends React .Component { render(){ return ( <div> <h1>现在的数量为{this .props.num}</h1> <button onClick={this.props.addNum} >+</ button> <button onClick={this .props.reduceNum} >-</button > <button onClick={this .props.addNumAsync } >+ Async</button> </ div> ) } } const mapStateProps = (state )=> { return {num :state} } const actionCreators = { addNum, reduceNum, addNumAsync };App = connect(mapStateProps, actionCreators)(App); export default App
react-redux极大的简化了我们使用redux的步骤,查看我的在线demo:Codesandbox
使用装饰器优化React-Redux中的connect 在未使用ES7的装饰器新特性时,我们是这样使用connect的,看起来确实有些繁琐,改进方案也很简单。
1 2 3 4 5 6 7 8 9 const mapStateProps = (state )=> { return {num :state} } const actionCreators = { addNum, reduceNum, addNumAsync };App = connect(mapStateProps, actionCreators)(App); export default App
1、我们的项目是用creact-react-app初始化的,使用装饰器是需要配置package.json中的plugins,于是我们npm run eject呼出个性化配置,然后安装npm install babel-plugin-transform-decorators-lagacy插件。 2、打开package.json,在“babel”中添加"plugins":["transform-decorators-legacy"] :
1 2 3 4 5 6 "babel" : { "presets" : [ "react-app" ], "plugins" :["transform-decorators-legacy" ] },
3、修改App.js 为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import React from 'react' ;import { connect } from 'react-redux' ;import { addNum, reduceNum, addNumAsync } from './index.redux' ;@connect( state=>({num :state}), { addNum, reduceNum, addNumAsync } ) class App extends React .Component { render(){ return ( <div> <h1>现在的数量为{this .props.num}</h1> <button onClick={this.props.addNum} >+</ button> <button onClick={this .props.reduceNum} >-</button > <button onClick={this .props.addNumAsync} >+ Async</button> </ div> ) } } export default App
为什么我们需要安装插件还需要配置babel才能使用装饰器呢,因为部分浏览器还未支持装饰器decorators用法。查看在线demo:Codesandbox
总结 这篇学习记录只是浅析了Redux的简单使用方式,并没有涉及到较为复杂的Redux用法,在项目开发中Redux的使用远不止这么简单,还需要进一步的摸索与实践,才能较好的掌握Redux这个强大的状态管理工具。
nice to meet you :)
参考资料:
Redux 中文文档
阮一峰Redux 入门教程(一):基本用法
阮一峰Redux 入门教程(二):中间件与异步操作
阮一峰Redux 入门教程(三):React-Redux 的用法