纯手硬撸Redux

栏目: IOS · Android · 发布时间: 5年前

内容简介:当今不管作为一个前端小白还是一个资深的前端攻城狮。如果不掌握几种前端框架(React,Vue,ng),都不好意思出去说自己是做前端。但是面对如此之多的前端框架,尤其是作为业界层出不穷的数据处理框架所以,有必要看看Redux是如何实现数据存储,又如何使得存储的数据被

当今不管作为一个前端小白还是一个资深的前端攻城狮。如果不掌握几种前端框架(React,Vue,ng),都不好意思出去说自己是做前端。但是面对如此之多的前端框架,尤其是 ReactVue 这种纯负责UI展示的架子来说。有一件事是绕不开的就是 前端的数据存储 问题。

作为业界层出不穷的数据处理框架 Redux (React的数据存储框架)又是不得不提起的。 Vue 的数据处理一般用 Vuex 。但是他的设计思路都是基于Redux等

所以,有必要看看Redux是如何实现数据存储,又如何使得存储的数据被 组件 获取,并且组件在触发CRUD的时候,能够及时更新数据呢。

我们就按照Redux的实现原理来剖析一下这些数据存储框架( Redux , Vuex )的原理。

接下里我们会用代码来实现 createStore() (Redux的核心概念)、 combineReducers() 、链接Redux和React的 connect() 、Redux的异步中间件 Redux Thunk

Redux

Redux是什么

用浅显的的话来概括Redux的作用,其实Redux就是一个 前端数据库

他将前端的 所有 数据(不管是从后台返回的还是前端自己用的数据,这些数据是你想共享的)存入一个 变量 。这个 变量 学名叫做 Redux Store (是一个js对象)。并且该变量对外界是只读的。如果想要改变改变量中的数据,需要发送一个 action 来通过特定的修改机制来 更新 Store 中的特定的值。

用后台操作数据库来类比:

//创建了一个名为Store的数据库 
//该操作等同于调用createStore()
CREATE DATABASE Store  

//创建一个名为Persons的表,该表包含的字段为Id,LastName,FirstName
//该操作等同在构建Store的时候,初始化state
CREATE TABLE Persons(Id int,LastName varchar(255),FirstName varchar(255))
复制代码

store 中的值发生变化的时候( state 的值发生变化),你的程序能够 察觉 到。当Redux配合React使用的时候,当state的值发生变化, React 组件能够接收到变化的消息,与该state有关系的组件就会按照 最新 的state来触发re-render。

纯手硬撸Redux

如上图展示,当 store 接收一个特定的 action 的时候,store需要一个 指定的方式 来更新 store 中特定的 state 。而这个特定的方式就是 reducer 的职责。它是一个 js函数 。用于规定,如何根据指定的key去更新 store 中指定的某些 state 。该函数在 store 创建的时候,做为参数传入到 createStore() 中。

//更新对应表中的指定字段
//reducer根据action来更新特定的state
UPDATE Persons SET FirstName = 'Fred' WHERE LastName = 'Wilson' 
复制代码

code

针对store,我们需要有一个大致的理解

  1. 获取当前 store 的最新的state
  2. dispatch 一个action,传入 reducer() ,用于计算最新的state
  3. 对store的变化设置监听函数

获取最新的state

我们创建了一个接收 reducre , initialState 作为参数的函数。该实现方式可以在调用 createStore() 之后,获取到store,调用该对象的 getState() 获取最新的state。

funtion createStore(reducer,initialState){
    var currentReducer = reducre;
    var currentState = iniitialState;
    return {
        getState(){
            return currentState;
        }
    }
}
复制代码

Dispatch action

接下来,我们要实现dispatch action.

funtion createStore(reducer,initialState){
    var currentReducer = reducre;
    var currentState = initialState;
    return {
        getState(){
            return currentState;
        }
        dispatch(action){
            currentState = currentReducer(currentState,action);
            return action;
        }
    }
}
复制代码

dispatch() 向指定的reducer中传入action和当前的state,用于根据指定的方式来 更新 store中的值。

设置监听函数

funtion createStore(reducer,initialState){
    var currentReducer = reducre;
    var currentState = initialState;
    var listener = ()={}
    return {
        getState(){
            return currentState;
        },
        dispatch(action){
            currentState = currentReducer(currentState,action);
            listener();
            return action;
        },
         subscribe(newListener) {
            listener = newListener;
        }
    }
    
}
复制代码

当需要触发一次action的时候,我们可以去调用已一个回调函数作为参数的 subscribe 如上代码所示:利用 subscibe() 的参数来设置store中的 listener ,从而使每次 action 被触发的时候,调用该函数,起到监听数据的作用。

代码实战

我们可以利用redux官网的例子来验证一下我们自己的redux。

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1
  case 'DECREMENT':
    return state - 1
  default:
    return state
  }
}
let store = createStore(counter)
store.subscribe(() =>
  console.log(store.getState())
  )
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'DECREMENT' })
复制代码

CombineReducers

combineReducre的作用

在上面的例子中,我使用reducer来算数。从代码上看, reducer() 看起来就是一个 switch() ,根据不同的key去处理不同的情况。

针对小的demo而已,这种处理方式很够用,但是如果项目比较大的话,这种处理方式,就会使得reducer变得冗长,且不易管理。

针对上面的问题, combineReducer() 就产生了,它能使将多个小的 reducer 合并成一个大的,最后供 Redux 使用。

编写自己的combineReducer

如下是官网的一些简单的示例:

// reducers.js
export default theDefaultReducer = (state = 0, action) => state;
export const firstNamedReducer = (state = 1, action) => state;
export const secondNamedReducer = (state = 2, action) => state;

 // Use ES6 object literal shorthand syntax to define the object shape
const rootReducer = combineReducers({
  theDefaultReducer,
  firstNamedReducer,
  secondNamedReducer
});
const store = createStore(rootReducer);
console.log(store.getState());
// {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2}
复制代码

定义函数

combineReducer 接收由很多小的reducer构成的 对象

function combineReducers(reducers){
    
}
复制代码

返回一个空reducer

从上面的例子中看到,调用 combineReducer 返回了一个 rootReducer ,作为 createStore() 的参数。也就是说,rootReducer其实就是一个简单的Redux reducer函数。

function combineReducers(reducers){
    return function combination(state={},action){
        
    }
}

复制代码

生成state

function combineReducers(reducers) {
  // 获取所有子reducer对应的key的名称
  const reducerKeys = Object.keys(reducers);
  return function combination(state = {}, action) {
   //零时变量
    const nextState = {}
    for (let i = 0; i < reducerKeys.length; i++) {
    //获取当前的key
    const key = reducerKeys[i];
    // 对应的reducer
    const reducer = reducers[key]
    // 获取未修改之前的state
    const previousStateForKey = state[key]
    // 通过redcuer计算之后的state
    const nextStateForKey = reducer(previousStateForKey, action)
    // 更新store
    nextState[key] = nextStateForKey;
    }
    return nextState;
        }
    }
复制代码

和React进行数据沟通

Redux 是一个独立的前端数据管理库。可以和很多框架一起使用,但是如果在React开发中,想使用Redux带来的 快感 ,可以是配合 react-redux 一起使用。

React Redux

React组件能够获取到store

首先,我们将react的root component 内嵌到以 store 做为props的 react-redux 组件中。这样就可以使得store能够在所有的react组件中 可见

import { Provider } from 'react-redux';
const store = createStore(myReducer);
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
    document.getElementById('root')
)
复制代码

在react组件中使用store

构建一个简单的组件

let AddTodo = ({ todos }) => {
  // 省去部分代码
}
复制代码

react-redux 有一个 connect() 用于将store connect 到你的React组件中。

const mapStateToProps = (state) => {
  return {
    todos: state.todos
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
} }
}
AddTodo = connect(mapStateToProps, mapDispatchToProps)(AddTodo)
复制代码

connect()自动 从store中拿对应的值,并且将拿到的值作为props传入到被 connect 的组件中。当store中的值发生变化,对应的props也会变化,从而触发组件的重新渲染。

代码实现connect

参数指定

connect() 被调用的时候,会 立即 返回一个新的函数。

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
      //something happens here
    } 
}
复制代码

函数输出结果

从上面的示例中,看到, connect() 函数接受一个react组件做完参数,并且也返回了一个 的组件。

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    //返回了一个新的组件
    return class extends React.Component {
      render() {
        return (
          <WrappedComponent
            {...this.props}
            />
            ) }
        }
    }
}
复制代码

如果对React开发比较熟悉的同学,很快就会定位到,这是利用了React的Hoc。或者可以参考,我写的关于HOC的看法。有兴趣的可以研究一下,封装一些公共组件很有用。

向返回的新组件中添加参数

由于使用 connect() 构建了一个 的组件,所以需要像组件中传入需要的数据,而这个数据的来源有3个地方。 使用 connect() 构建的新组件的数据来源有3个地方。

store.getState()
function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    return class extends React.Component {
      render() {
        return (
          <WrappedComponent
            {...this.props}//获取原始组件的值
            {...mapStateToProps(store.getState(), this.props)}//获取store存贮的值
            {...mapDispatchToProps(store.dispatch, this.props)}//触发动作
          />
        ) }
    } 
  }
}
复制代码

如何在组件中获取到store的实例

从上面的代码中,有的同学可能存在疑问,为什么我没有找到定义或者存储store的地方,但是却可以直接使用。在进行react和redux数据关联的时候,使用了 Providerroot component 进行包装。其实这里就是将store进行了全局注册,你也可以认为是函数中的全局变量。具体如何实现的,可以参考react中的Context。

订阅store

通过上述的操作,我们现在已经可以在我们自己的组件获取到store中的值,并且能够在组件中 dispatch 一些action来更新对应的state。 如果在实际项目中,需要用到针对某个事件变化,进行组件的强制渲染。可以和该事件和store中的值,进行 绑定 ,但凡触发,就更新组件状态。

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    return class extends React.Component {
      render() {
        return (
          <WrappedComponent
            {...this.props}//获取原始组件的值
            {...mapStateToProps(store.getState(), this.props)}//获取store存贮的值
            {...mapDispatchToProps(store.dispatch, this.props)}//触发动作
          />
        ) 
          
      }
      componentDidMount() {
        this.unsubscribe = store.subscribe(this.handleChange.bind(this))
      }
      componentWillUnmount() {
        this.unsubscribe()
        }
      handleChange() {
        this.forceUpdate()//利用react组件的事件,来强制重新渲染组件
        }
    } 
  }
}
复制代码

Redux thunk Middleware

Redux 是一个极其简单的数据存贮状态库,所以在他自己的代码实现中,只是针对了同步操作。但是对于前端来说,如果仅仅只是同步操作的话,一些后台接口返回的数据就无法存入到store中,这样70%的数据就无法享受使用redux的快感。

Redux Thunk Middleware 完美解决了 Redux 异步数据存贮问题。(redux的异步处理,不只是redux,thunk一种,有很多)

thunk的代码实现

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    //如果action是一个函数,将会被处理,进行异步操作,同时可以在异步的结果中获取
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
复制代码

这就是thunk的所有代码。意不意外,惊不惊喜。

核心思路

其实核心代码就是将Redux作为一个 中间件

if (typeof action === 'function') {
  return action(dispatch, getState, extraArgument);
}
复制代码

Redux Thunk 配置在你的项目中,每次 dispatch 一个action的时候,上面的代码都会执行。如果被触发的action是一个函数,上面的代码就会去判断,并且会调用以 dispatch 作为参数的函数(也就是异步函数)。

总结:

  1. 当redux没有配置中间件的时候,action总是一个 简单的js对象
  2. 当使用了中间件的时候,action可以是一个对象也可以是函数。

使用redux thunk

使用redux thunk创建的action格式如下:

function loadPostsAction() {
    return (dispatch, getState) => {
        // 进行异步处理
    }; 
}
复制代码

调用上述函数,又返回了一个能够获取到redux dispatch函数的函数。

使用异步action来处理异步数据。

function loadPostsAction() {
    return (dispatch, getState) => {
       get("/api/posts").then(
       (paylod)=>dispath({type:"success",paylod}),
       (err)=>dispath({typs:"err",})
       )
    }; 
}
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

反应式设计模式

反应式设计模式

Roland Kuhn、Brian Hanafee、Jamie Allen / 何品、邱嘉和、王石冲、林炜翔审校 / 清华大学出版社 / 2019-1-1 / 98.00 元

《反应式设计模式》介绍反应式应用程序设计的原则、模式和经典实践,讲述如何用断路器模式将运行缓慢的组件与其他组件隔开、如何用事务序列(Saga)模式实现多阶段事务以及如何通过分片模式来划分数据集,分析如何保持源代码的可读性以及系统的可测试性(即使在存在许多潜在交互和失败点的情况下)。 主要内容 ? “反应式宣言”指南 ? 流量控制、有界一致性、容错等模式 ? 得之不易的关于“什么行不通”的经验 ? ......一起来看看 《反应式设计模式》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具