上一篇文章我们手写了一个Redux,但是单纯的Redux只是一个状态机,是没有UI呈现的,所以一般我们使用的时候都会配合一个UI库,比如在React中使用Redux就会用到React-Redux这个库。这个库的作用是将Redux的状态机和React的UI呈现绑定在一起,当你dispatch action改变state的时候,会自动更新页面。本文还是从它的基本使用入手来自己写一个React-Redux,然后替换官方的NPM库,并保持功能一致。
本文全部代码已经上传GitHub,大家可以拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/react-redux
基本用法下面这个简单的例子是一个计数器,跑起来效果如下:
要实现这个功能,首先我们要在项目里面添加react-redux库,然后用它提供的Provider包裹整个ReactApp的根组件:
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux' import store from './store' import App from './App'; ReactDOM.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root') );上面代码可以看到我们还给Provider提供了一个参数store,这个参数就是Redux的createStore生成的store,我们需要调一下这个方法,然后将返回的store传进去:
import { createStore } from 'redux'; import reducer from './reducer'; let store = createStore(reducer); export default store;上面代码中createStore的参数是一个reducer,所以我们还要写个reducer:
const initState = { count: 0 }; function reducer(state = initState, action) { switch (action.type) { case 'INCREMENT': return {...state, count: state.count + 1}; case 'DECREMENT': return {...state, count: state.count - 1}; case 'RESET': return {...state, count: 0}; default: return state; } } export default reducer;这里的reduce会有一个初始state,里面的count是0,同时他还能处理三个action,这三个action对应的是UI上的三个按钮,可以对state里面的计数进行加减和重置。到这里其实我们React-Redux的接入和Redux数据的组织其实已经完成了,后面如果要用Redux里面的数据的话,只需要用connectAPI将对应的state和方法连接到组件里面就行了,比如我们的计数器组件需要count这个状态和加一,减一,重置这三个action,我们用connect将它连接进去就是这样:
import React from 'react'; import { connect } from 'react-redux'; import { increment, decrement, reset } from './actions'; function Counter(props) { const { count, incrementHandler, decrementHandler, resetHandler } = props; return ( <> <h3>Count: {count}</h3> <button onClick={incrementHandler}>计数+1</button> <button onClick={decrementHandler}>计数-1</button> <button onClick={resetHandler}>重置</button> </> ); } const mapStateToProps = (state) => { return { count: state.count } } const mapDispatchToProps = (dispatch) => { return { incrementHandler: () => dispatch(increment()), decrementHandler: () => dispatch(decrement()), resetHandler: () => dispatch(reset()), } }; export default connect( mapStateToProps, mapDispatchToProps )(Counter)上面代码可以看到connect是一个高阶函数,他的第一阶会接收mapStateToProps和mapDispatchToProps两个参数,这两个参数都是函数。mapStateToProps可以自定义需要将哪些state连接到当前组件,这些自定义的state可以在组件里面通过props拿到。mapDispatchToProps方法会传入dispatch函数,我们可以自定义一些方法,这些方法可以调用dispatch去dispatch action,从而触发state的更新,这些自定义的方法也可以通过组件的props拿到,connect的第二阶接收的参数是一个组件,我们可以猜测这个函数的作用就是将前面自定义的state和方法注入到这个组件里面,同时要返回一个新的组件给外部调用,所以connect其实也是一个高阶组件。
到这里我们汇总来看下我们都用到了哪些API,这些API就是我们后面要手写的目标:
Provider: 用来包裹根组件的组件,作用是注入Redux的store。
createStore: Redux用来创建store的核心方法,我们另一篇文章已经手写过了。
connect:用来将state和dispatch注入给需要的组件,返回一个新组件,他其实是个高阶组件。
所以React-Redux核心其实就两个API,而且两个都是组件,作用还很类似,都是往组件里面注入参数,Provider是往根组件注入store,connect是往需要的组件注入state和dispatch。
在手写之前我们先来思考下,为什么React-Redux要设计这两个API,假如没有这两个API,只用Redux可以吗?当然是可以的!其实我们用Redux的目的不就是希望用它将整个应用的状态都保存下来,每次操作只用dispatch action去更新状态,然后UI就自动更新了吗?那我从根组件开始,每一级都把store传下去不就行了吗?每个子组件需要读取状态的时候,直接用store.getState()就行了,更新状态的时候就store.dispatch,这样其实也能达到目的。但是,如果这样写,子组件如果嵌套层数很多,每一级都需要手动传入store,比较丑陋,开发也比较繁琐,而且如果某个新同学忘了传store,那后面就是一连串的错误了。所以最好有个东西能够将store全局的注入组件树,而不需要一层层作为props传递,这个东西就是Provider!而且如果每个组件都独立依赖Redux会破坏React的数据流向,这个我们后面会讲到。
React的Context API