目录
前言
Redux
作为React
的状态管理工具, 在开发大型应用时已不可缺少, 为了更深入的了解Redux
的整个实现机制, 决定从头开始, 实现实现一个具有基础功能的Redux
欢迎star/fork
初始化项目
1.全局安装脚手架
npm install -g create-react-app复制代码
2.创建项目
create-react-app mini-redux复制代码
3.项目目录
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复制代码
实现Redux基础功能
1.实现Redux
新建~/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'// reducerexport 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修改,都会执行listenerstore.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
来使用
2.结合React使用
下面将mini-react
和react
组件结合使用, 修改index.redux.js
如下
const ADD = 'ADD'const REMOVE = 'REMOVE'// reducerexport 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'// 初始化reduxconst store = createStore(counter)function render() { ReactDOM.render(, 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 (); }}export default App;复制代码初始值为{num}
如上图, 我们就可以在React
组件中修改mini-redux
的状态了
实现React-Redux
上面我们已经,实现了Redux
的功能,并且且可以和React
结合使用了, 但是这种与React
的链接的方式非常繁琐,高度耦合, 在日常开发中不会这样用, 我们会使用 react-redux
库来连接React
(如果不了解react-redux
可以看看这篇), 下面我们就来实现一个简易的react-redux
1.context
实现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 () }}class Navbar extends React.Component { // 限制类型, 必须 static contextTypes = { user: PropTypes.string } render() { console.log(this.context) return (Sidebar
{this.context.user} Navbar) }}class Page extends React.Component { // 限制类型, 必须 static childContextTypes = { user: PropTypes.string } constructor(props){ super(props) this.state = { user: 'Jack'} } getChildContext() { return this.state } render() { return () }}export default Page复制代码我是{this.state.user}
2.react-readux
react-redux
中有两个是我们常用的组件, 分别是connect
和Provider
, connect
用于组件获取redux
里面的数据(state
和action
), Provider
用于把store
置于context
, 让所有的子元素可以获取到store
, 下面分别实现connect
和provider
实现Provider
新建~/src/mini-redux/mini-react-redux
, 代码如下
import React from 'react'import PropTypes from 'prop-types'// 把store放到context里, 所有的子元素可以直接取到storeexport 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( (), 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 (); }}App = connect(state => ({ num: state}), {add, remove})(App)export default App;复制代码初始值为{this.props.num}
验证结果, 上面实现的Provider
成功对接connect
实现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} }}复制代码
在上面代码中, 我们还需要在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中间件机制
实现applyMiddleware
在平常使用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 (); }}App = connect(state => ({ num: state}), {add, remove, addAsync})(App)export default App;复制代码初始值为{this.props.num}
然后就可以验证啦
实现redux中间件
上面我们使用了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 (); }}App = connect(state => ({ num: state}), {add, remove, addAsync, addTwice})(App)复制代码now num is {this.props.num}
大功告成!