Redux
作为React
的状态管理工具, 在开发大型应用时已不可缺少, 为了更深入的了解Redux
的整个实现机制, 决定从头开始, 实现实现一个具有基础功能的Redux
npm install -g create-react-app
create-react-app mini-redux
mini-react
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ └── favicon.ico
│ └── index.html
│ └── manifest.json
└── src
└── App.css
└── App.js
└── App.test.js
└── index.css
└── index.js
└── logo.svg
└── registerServiceWorker.js
新建~/src/mini-redux/mini-redux.js
, redux
会对外暴露一个createStore
的方法,接受reducer
作为参数
export function createStore(reducer) { let currentState = {} let currentListeners = [] function getState() { return currentState } function subscribe(listener) { currentListeners.push(listener) } function dispatch(action) { currentState = reducer(currentState, action) currentListeners.forEach(v => v()) return action } dispatch({type: ‘@REACT_FIRST_ACTION‘}) //初始化state return { getState, subscribe, dispatch} }
以上, 我们就已经实现了redux
的基础功能, 下面来调用我们实现的mini-redux
, 检验是否达到预期. 新建~/src/index.redux.js
import { createStore } from ‘./mini-redux/mini-redux‘ const ADD = ‘ADD‘ const REMOVE = ‘REMOVE‘ // reducer export function counter(state=0, action) { switch (action.type) { case ADD: return state + 1 case REMOVE: return state - 1 default: return 10 } } export function add() { return {type: ‘ADD‘} } export function remove() { return {type: ‘REMOVE‘} } const store = createStore(counter) const init = store.getState() console.log(`开始数值:${init}`) function listener(){ const current = store.getState() console.log(`现在数值:${current}`) } // 订阅,每次state修改,都会执行listener store.subscribe(listener) // 提交状态变更的申请 store.dispatch({ type: ‘ADD‘ }) store.dispatch({ type: ‘ADD‘ }) store.dispatch({ type: ‘REMOVE‘ }) store.dispatch({ type: ‘REMOVE‘ })
在index.js
中引入以上文件以执行, 查看控制台,可以看到如下log
信息
开始数值:10 index.redux.js:27 现在数值:11 index.redux.js:31 现在数值:12 index.redux.js:31 现在数值:11 index.redux.js:31 现在数值:10 index.redux.js:31
至此,我们已经实现了redux
的功能, 但是离我们的预期还差的很远, 因为我们需要结合react
来使用
下面将mini-react
和react
组件结合使用, 修改index.redux.js
如下
const ADD = ‘ADD‘ const REMOVE = ‘REMOVE‘ // reducer export function counter(state=0, action) { switch (action.type) { case ADD: return state + 1 case REMOVE: return state - 1 default: return 10 } } export function add() { return {type: ‘ADD‘} } export function remove() { return {type: ‘REMOVE‘} }
index.js
文件初始化redux
import { createStore } from ‘./mini-redux/mini-redux‘ import { counter } from ‘./index.redux‘ // 初始化redux const store = createStore(counter) function render() { ReactDOM.render(<App store={store} />, document.getElementById(‘root‘)); } render() // 每次修改状态,从新渲染页面 store.subscribe(render)
App.js
文件中我们就可以调用redux
啦
import {add, remove} from ‘./index.redux‘ class App extends Component { render() { const store = this.props.store // 获取当前值 const num = store.getState() return ( <div className="App"> <p>初始值为{num}</p> <button onClick={() => store.dispatch(add())}>Add</button> <button onClick={() => store.dispatch(remove())}>Remove</button> </div> ); } } export default App;
如上图, 我们就可以在React
组件中修改mini-redux
的状态了
上面我们已经,实现了Redux
的功能,并且且可以和React
结合使用了, 但是这种与React
的链接的方式非常繁琐,高度耦合, 在日常开发中不会这样用, 我们会使用 react-redux
库来连接React
(如果不了解react-redux
可以看看这篇博客), 下面我们就来实现一个简易的react-redux
实现react-redux
前, 我们要了解一下react
的 context
(不了解可以查看文档), react-redux
的实现就利用了context
机制. 下面通过一个例子,了解context
的用法.
新建~/src/mini-redux/context.test.js
import React from ‘react‘ import PropTypes from ‘prop-types‘ // context是全局的, 组件里声明, 所有子元素可以直接获取 class Sidebar extends React.Component { render(){ return ( <div> <p>Sidebar</p> <Navbar></Navbar> </div> ) } } class Navbar extends React.Component { // 限制类型, 必须 static contextTypes = { user: PropTypes.string } render() { console.log(this.context) return ( <div>{this.context.user} Navbar</div> ) } } class Page extends React.Component { // 限制类型, 必须 static childContextTypes = { user: PropTypes.string } constructor(props){ super(props) this.state = {user: ‘Jack‘} } getChildContext() { return this.state } render() { return ( <div> <p>我是{this.state.user}</p> <Sidebar/> </div> ) } } export default Page
react-redux
中有两个是我们常用的组件, 分别是connect
和Provider
, connect
用于组件获取redux
里面的数据(state
和action
), Provider
用于把store
置于context
, 让所有的子元素可以获取到store
, 下面分别实现connect
和provider
新建~/src/mini-redux/mini-react-redux
, 代码如下
import React from ‘react‘ import PropTypes from ‘prop-types‘ // 把store放到context里, 所有的子元素可以直接取到store export class Provider extends React.Component{ // 限制数据类型 static childContextTypes = { store: PropTypes.object } getChildContext(){ return { store:this.store } } constructor(props, context){ super(props, context) this.store = props.store } render(){ // 返回所有子元素 return this.props.children } }
下面验证Provider
是否能实现预期功能, 修改~/src/index.js
文件如下
import React from ‘react‘; import ReactDOM from ‘react-dom‘; import ‘./index.css‘; import App from ‘./App‘; import { createStore } from ‘./mini-redux/mini-redux‘ import { Provider } from ‘./mini-redux/mini-react-redux‘ import { counter } from ‘./index.redux‘ const store = createStore(counter) ReactDOM.render( (<Provider store={store}><App/></Provider>), document.getElementById(‘root‘) )
最后我们还要修改~/src/App.js
文件中获取store
数据的方式, 改成使用connect
获取, 但是因为还没有实现connect
, 所有我们暂使用原react-redux
的connect
组件验证上面实现的Provider
import React, { Component } from ‘react‘; import { connect } from ‘react-redux‘ import {add, remove} from ‘./index.redux‘ class App extends Component { render() { return ( <div className="App"> <p>初始值为{this.props.num}</p> <button onClick={this.props.add}>Add</button> <button onClick={this.props.remove}>Remove</button> </div> ); } } App = connect(state => ({num: state}), {add, remove})(App) export default App;
验证结果, 上面实现的Provider
成功对接connect
上面我们实现了Provider
, 但是connect
仍然用的是原版react-redux
的connect
, 下面就来在~/src/mini-redux/mini-react-redux.js
文件中添加一个connect
方法
import React from ‘react‘ import PropTypes from ‘prop-types‘ import {bindActionCreators} from ‘./mini-redux‘ // connect 负责链接组件,给到redux里的数据放到组件的属性里 // 1. 负责接受一个组件,把state里的一些数据放进去,返回一个组件 // 2. 数据变化的时候,能够通知组件 export const connect = (mapStateToProps = state=>state, mapDispatchToProps = {}) => (WrapComponent) => { return class ConnectComponent extends React.Component{ static contextTypes = { store:PropTypes.object } constructor(props, context){ super(props, context) this.state = { props:{} } } componentDidMount(){ const {store} = this.context store.subscribe(()=>this.update()) this.update() } update(){ // 获取mapStateToProps和mapDispatchToProps 放入this.props里 const {store} = this.context const stateProps = mapStateToProps(store.getState()) // 方法不能直接给,因为需要dispatch const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch) this.setState({ props:{ ...this.state.props, ...stateProps, ...dispatchProps } }) } render(){ return <WrapComponent {...this.state.props}></WrapComponent> } } }
在上面代码中, 我们还需要在mini-redux.js
中添加一个bindActionCreators
方法, 用于使用dispatch
包裹包裹actionCreator
方法, 代码如下
...... function bindActionCreator(creator, dispatch){ return (...args) => dispatch(creator(...args)) } export function bindActionCreators(creators,dispatch){ let bound = {} Object.keys(creators).forEach(v=>{ let creator = creators[v] bound[v] = bindActionCreator(creator, dispatch) }) return bound } ......
最后我们将~/src/App.js
中的connect
换成上面完成的connect
, 完成测试
import { connect } from ‘./mini-redux/mini-react-redux‘
在平常使用redux
时, 我们会利用各种中间件来扩展redux
功能, 比如使用redux-thunk
实现异步提交action
, 现在来给我们的mini-redux
添加中间件机制
修改~/src/mini-redux/mini-redux.js
代码如下
export function createStore(reducer, enhancer) { if (enhancer) { return enhancer(createStore)(reducer) } let currentState = {} let currentListeners = [] function getState() { return currentState } function subscribe(listener) { currentListeners.push(listener) } function dispatch(action) { currentState = reducer(currentState, action) currentListeners.forEach(v => v()) return action } //初始化state dispatch({type: ‘@REACT_FIRST_ACTION‘}) return { getState, subscribe, dispatch} } export function applyMiddleware(middleware) { return createStore => (...args) => { const store = createStore(...args) let dispatch = store.dispatch const midApi = { getState: store.getState, dispatch: (...args) => dispatch(...args) } dispatch = middleware(midApi)(store.dispatch) return { ...store, dispatch } } } ......
以上我们就给mini-redux
添加了中间件机制了, 下面我们就来使用中间件, 进行验证. 由于我们开没有自己的中间件, 现在使用redux-thunk
来实现一个异步提交action
修改~/src/index.js
...... import { createStore, applyMiddleware } from ‘./mini-redux/mini-redux‘ import thunk from ‘redux-thunk‘ const store = createStore(counter, applyMiddleware(thunk)) ......
修改~/src/index.redux.js
, 添加一个异步方法
export function addAsync() { return dispatch => { setTimeout(() => { dispatch(add()); }, 2000); }; }
最后我们要~/src/App.js
中引入这个异步方法, 修改如下
...... import React, { Component } from ‘react‘; import { connect } from ‘./mini-redux/mini-react-redux‘ import {add, remove, addAsync} from ‘./index.redux‘ class App extends Component { render() { return ( <div className="App"> <p>初始值为{this.props.num}</p> <button onClick={this.props.add}>Add</button> <button onClick={this.props.remove}>Remove</button> <button onClick={this.props.addAsync}>AddAsync</button> </div> ); } } App = connect(state => ({num: state}), {add, remove, addAsync})(App) export default App;
然后就可以验证啦
上面我们使用了redux-thunk
中间件, 为何不自己写一个呢
新建~/src/mini-redux/mini-redux-thunk.js
const thunk = ({dispatch, getState}) => next => action => { // 如果是函数,执行一下,参数是dispatch和getState if (typeof action==‘function‘) { return action(dispatch,getState) } // 默认,什么都没干, return next(action) } export default thunk
将~/src/index.js
中的thunk
换成上面实现的thunk
, 看看程序是否还能正确运行
在上面的基础上, 我们再实现一个arrThunk
中间件, 中间件提供提交一个action
数组的功能
新建~/src/mini-redux/mini-redux-arrayThunk.js
const arrayThunk = ({dispatch,getState})=>next=>action=>{ if (Array.isArray(action)) { return action.forEach(v=>dispatch(v)) } return next(action) } export default arrayThunk
上面我们实现的中间件机制,只允许添加一个中间件, 这不能满足我们日常开发的需要
修改~/src/mini-redux/mini-redux.js
文件
...... // 接收中间件 export function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = store.dispatch const midApi = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const middlewareChain = middlewares.map(middleware=>middleware(midApi)) dispatch = compose(...middlewareChain)(store.dispatch) return { ...store, dispatch } } } // compose(fn1,fn2,fn3) ==> fn1(fn2(fn3)) export function compose(...funcs){ if (funcs.length==0) { return arg=>arg } if (funcs.length==1) { return funcs[0] } return funcs.reduce((ret,item)=> (...args)=>ret(item(...args))) } ......
最后我们将之前实现的两个中间件thunk
,arrThunk
同时使用, 看看上面实现的多中间件合并是否完成
修改~/src/index.js
... import arrThunk from ‘./mini-redux/mini-redux-arrThunk‘ const store = createStore(counter, applyMiddleware(thunk, arrThunk)) ...
在~/src/index.redux.js
中添加一个addTwice
action生成器
... export function addTwice() { return [{type: ‘ADD‘}, {type: ‘ADD‘}] } ...
~/src/App.js
中增加一个addTwice
的按钮, 修改相应代码
import {add, remove, addAsync, addTwice} from ‘./index.redux‘ class App extends Component { render() { return ( <div className="App"> <p>now num is {this.props.num}</p> <button onClick={this.props.add}>Add</button> <button onClick={this.props.remove}>Remove</button> <button onClick={this.props.addAsync}>AddAsync</button> <button onClick={this.props.addTwice}>addTwice</button> </div> ); } } App = connect(state => ({num: state}), {add, remove, addAsync, addTwice})(App)
大功告成!
深入理解redux原理,从零开始实现一个简单的redux(包括react-redux, redux-thunk)
原文:https://www.cnblogs.com/shaoshuai0305/p/12890772.html