博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入理解redux原理,从零开始实现一个简单的redux(包括react-redux, redux-thunk)
阅读量:6481 次
发布时间:2019-06-23

本文共 11903 字,大约阅读时间需要 39 分钟。

目录

前言

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-reactreact组件结合使用, 修改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 (            

初始值为{num}

); }}export default App;复制代码

如上图, 我们就可以在React组件中修改mini-redux的状态了

实现React-Redux

上面我们已经,实现了Redux的功能,并且且可以和React结合使用了, 但是这种与React的链接的方式非常繁琐,高度耦合, 在日常开发中不会这样用, 我们会使用 react-redux库来连接React(如果不了解react-redux可以看看这篇), 下面我们就来实现一个简易的react-redux

1.context

实现react-redux前, 我们要了解一下reactcontext(不了解可以查看), 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 (      

Sidebar

) }}class Navbar extends React.Component { // 限制类型, 必须 static contextTypes = { user: PropTypes.string } render() { console.log(this.context) return (
{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 (

我是{this.state.user}

) }}export default Page复制代码

2.react-readux

react-redux中有两个是我们常用的组件, 分别是connectProvider, connect用于组件获取redux里面的数据(stateaction), Provider用于把store置于context, 让所有的子元素可以获取到store, 下面分别实现connectprovider

实现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-reduxconnect组件验证上面实现的Provider

import React, { Component } from 'react';import { connect } from 'react-redux'import {add, remove} from './index.redux'class App extends Component {    render() {        return (            

初始值为{this.props.num}

); }}App = connect(state => ({
num: state}), {add, remove})(App)export default App;复制代码

验证结果, 上面实现的Provider成功对接connect

实现connect

上面我们实现了Provider, 但是connect仍然用的是原版react-reduxconnect, 下面就来在~/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 (            

初始值为{this.props.num}

); }}App = connect(state => ({
num: state}), {add, remove, addAsync})(App)export default App;复制代码

然后就可以验证啦

实现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 (            

now num is {this.props.num}

); }}App = connect(state => ({
num: state}), {add, remove, addAsync, addTwice})(App)复制代码

大功告成!

转载地址:http://qczuo.baihongyu.com/

你可能感兴趣的文章
论程序员加班的害处
查看>>
基于HTML5的WebGL设计汉诺塔3D游戏
查看>>
WPF资料链接
查看>>
再次更新
查看>>
利用Windows自带的Certutil查看文件MD5
查看>>
开篇,博客的申请理由
查看>>
[JSOI2008]星球大战starwar BZOJ1015
查看>>
iOS项目分层
查看>>
IntelliJ IDEA 注册码
查看>>
String字符串的截取
查看>>
Shell编程-环境变量配置文件
查看>>
Struts2和Spring MVC的区别
查看>>
理解Javascript参数中的arguments对象
查看>>
git代码冲突
查看>>
git bash 风格调整
查看>>
linux操作系统加固软件,系统安全:教你Linux操作系统的安全加固
查看>>
linux中yum源安装dhcp,24.Linux系统下动态网络源部署方法(dhcpd)
查看>>
HDOJ-1010 Tempter of the Bone
查看>>
日本开设无人机专业,打造无人机“人才市场”
查看>>
190行代码实现mvvm模式
查看>>