纯手硬撸Redux

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

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

当今不管作为一个前端小白还是一个资深的前端攻城狮。如果不掌握几种前端框架(React,Vue,ng),都不好意思出去说自己是做前端。但是面对如此之多的前端框架,尤其是 React 、 Vue 这种纯负责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数据关联的时候,使用了 Provider 对 root 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",})
       )
    }; 
}
复制代码

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

查看所有标签

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

微商团队管理实战手册

微商团队管理实战手册

杜一凡 / 人民邮电出版社 / 2015-11 / 45.00元

回顾淘宝,用了10年时间才发展了不到1000万的卖家,再看微商,其仅一年时间就拥有了超过1000万的卖家。进入2015年,微商的发展之路虽有小坎坷,但前景依然被看好。然而任何一个想要做大、做强的微商都要以团队形式来发展,独立的个体只会举步维艰。 本书全面解读微商团队管理的营销书。全书共分为六章,分别从微商团队的商业秘密、微商团队的战略布局、管理基本功、建立高效团队、精通管理工具、未来发展等方......一起来看看 《微商团队管理实战手册》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

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

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试