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)
大功告成!