• Redux的内幕(一)

    • 一个问题引发的血案
    • 重振旗鼓
    • 基础了解
    • 修修边幅
    • 心中有疑惑
    • 未待完续
  • 相关链接

Redux的内幕(一)

一个问题引发的血案

博主在面试的过程中,面试官问 : “看你简历,Vue和React都使用过,你能说一下 Vue和React的区别嘛?”, 然后吧唧吧唧说了一下,于是!血案发生了,当我答道Vuex和Redux的时候,面试官问了一句,为什么Redux总是要返回一个新的 state ?返回旧的 state 为什么不行 ?面试结果不用说,GG了。

重振旗鼓

过了大半个月,自己总结面试经验的时候,把Redux的源码看了一遍,ojbk,看的晕头转向,然后去github上看了一些大哥们的解读,再自己总结一哈,写个专栏,用于自己以后的复习

基础了解

Redux是什么?

Redux 是 JavaScript 状态容器,提供可预测化的状态管理方案, 官网里是这么介绍的 :

  // Redux is a predictable state container for JavaScript apps.
复制代码

那么它能做什么?

  // It helps you write applications that behave consistently, run in different environments (client, server, and native) and are easy to test.// On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.
复制代码

三大原则

  • 单一数据源 : 整个应用的 state 都存储在一颗 state tree 中,并且只存在与唯一一个 store 中

  • state 是只读的 : 唯一改变 state 的方法只能通过触发 action,然后通过 action 的 type 进而分发 dispatch 。不能直接改变应用的状态

  • 状态修改均由纯函数完成 : 为了描述 action 如何改变 state tree,需要编写 reducers

修修边幅

这里我们先来了解一下store、middleware、action、reducer等知识

store

这里的 store 是由 Redux 提供的 createStore(reducers, preloadedState, enhancer) 方法生成。从函数签名看出,要想生成 store,必须要传入 reducers,同时也可以传入第二个可选参数初始化状态(preloadedState)。第三个参数一般为中间件 applyMiddleware(thunkMiddleware),看看代码,比较直观

  import { createStore, applyMiddleware } from 'redux'import thunkMiddleware from 'redux-thunk' // 这里用到了redux-thunkconst store = createStore(reducers,state,applyMiddleware(thunkMiddleware) // applyMiddleware首先接收thunkMiddleware作为参数,两者组合成为一个新的函数(enhance))
复制代码

redux 中最核心的 API 就是 —— createStore, 通过 createStore 方法创建的store是一个对象,它本身包含4个方法 :

  • getState() : 获取 store 中当前的状态。

  • dispatch(action) : 分发一个 action,并返回这个 action,这是唯一能改变 store 中数据的 方式。

  • subscribe(listener) : 注册一个监听者,它在 store 发生变化时被调用。

  • replaceReducer(nextReducer) : 更新当前 store 里的 reducer,一般只会在开发模式中调用该方法。

middleware

下图中表达的是Redux 中一个简单的同步数据流动场景,点击 button 后,在回调中分发一个 action, reducer 收到 action 后,更新 state 并通知 view 重新渲染。

单向数据流,看着没什么问题。 但是,如果需要打印每一个 action 信息来调试,就得去改 dispatch 或者 reducer 实现,使其具有 打印日志的功能。

又比如,点击 button 后,需要先去服务端请求数据,只有等数据返回后,才能重新渲染 view,此时我们希望 dispatch 或 reducer 拥有异步请求的功能。再比如,需要异步请求数据返回后,打印一条日志,再请求数据,再打印日志,再渲染。

面对多样的业务场景,单纯地修改 dispatch 或 reducer 的代码显然不具有普适性,Redux 借鉴了 Node.js Koa 里 middleware 的思想,Redux 中 reducer 更关心的是数据的转化逻辑,所以 middleware 就是为了增强 dispatch 而出现的。

Action

  // 引用官网的介绍// Actions are payloads of information that send data from your application to your store. // They are the only source of information for the store// You send them to the store using store.dispatch().
复制代码

Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。简单来说,Action就是一种消息类型,他告诉Redux是时候该做什么了,并带着相应的数据传到Redux内部。

Action就是一个简单的对象,其中必须要有一个type属性,用来标志动作类型(reducer以此判断要执行的逻辑),其他属性用户可以自定义。如:

  const START_FETCH_API = 'START_FETCH_API'
复制代码
  {type: START_FETCH_API,data: {id: itemId,value: 'I am Value'}}
复制代码

Action Creator

看看官网中的介绍 : Action Creator are exactly that—functions that create actions. It's easy to conflate the terms “action” and “action creator”, so do your best to use the proper term。 也就是说 : Redux 中的 Action Creator 只是简单的返回一个 Action

  function fetchStartRequestApi(jsondata) {return {type: START_FETCH_API,data: jsondata}}
复制代码

我们知道,Redux 由 Flux 演变而来,在传统的 Flux 中, Action Creators 被调用之后经常会触发一个dispatch。比如:

  function fetchStartRequestApiDispatch(jsondata) {const action = {type: START_FETCH_API,data: jsondata}dispatch(action)}
复制代码

但是,在Redux中,我们只需要把 Action Creators 返回的结果传给 dispatch() ,就完成了发起一个dispatch 的过程,甚至于 创建一个 被绑定的 Action Creators 来自动 dispatch

  // example 1dispatch(fetchStartRequestApi(jsondata))// example 2const boundFetchStartRequestApiDispatch = jsondata => dispatch(fetchStartRequestApi(jsondata))
复制代码

这里有人就要昏厥了,dispatch() 是个啥?其实前面就讲过了,通过 createStore() 创建的 store 对象,他有一个方法 : dispatch(action),store 里能直接通过 store.dispatch() 调用 dispatch() 方法,但是多数情况下,我们都会使用 react-redux 提供的 connect() 帮助器来调用。bindActionCreators() 可以自动把多个 action 创建函数绑定到 dispatch() 方法上。

Reducers

  // 引用官网的介绍// Reducers specify how the application's state changes in response to actions sent to the store. // Remember that actions only describe what happened, but don't describe how the application's state changes
复制代码

上边也说过了,Reducers必须是一个纯函数,它根据action处理state的更新,如果没有更新或遇到未知action,则返回旧state;否则返回一个新state对象。__注意:不能修改旧state,必须先拷贝一份state,再进行修改,也可以使用Object.assign函数生成新的state。__具体为什么,我们读源码的时候就知道啦~

永远不要在 reducer 里做这些操作:

  • 修改传入参数;

  • 执行有副作用的操作,如 API 请求和路由跳转;

  • 调用非纯函数,如 Date.now() 或 Math.random();

下边上个例子代码,帮助消化,发送请求,获取音乐列表

  // action.js/** action 类型*/  export const START_FETCH_API = 'START_FETCH_API'export const STOP_FETCH_API = 'STOP_FETCH_API'export const RECEIVE_DATA_LIST = 'RECEIVE_DATA_LIST'export const SET_OTHER_FILTERS = 'SET_OTHER_FILTERS'/** 其它的常量*/export const otherFilters = {SHOW_ALL: 'SHOW_ALL',SHOW_ACTIVE: 'SHOW_ACTIVE'}/** action 创建函数*/export function startFetchApi() {return {type: START_FETCH_API}}export function stopFetchApi() {return {type: STOP_FETCH_API}}export function receiveApi(jsondata) {return {type: RECEIVE_DATA_LIST,data: jsondata}}export function setOtherFilters (filter) {return {type: SET_OTHER_FILTERS,data: filter}}// 异步export const fetchMusicListApi = (music_id) => dispatch => {dispatch(startFetchApi())fetch({url: url,method: 'POST',data: {music_id: music_id}}).then((res) => {dispatch(stopFetchApi())dispatch(receiveApi())}).catch((err) => {console.log(err)})}
复制代码
  // reducers.js// 引入 action.jsimport { otherFilters } from './action'// 初始 stateconst initialState = {otherFilters: otherFilters.SHOW_ALL,list: [],isFetching: false}function reducers(state, action) {switch(action.type) {case SET_OTHER_FILTERS: return Object.assign({}, state, {otherFilters: action.payload.data})case START_FETCH_API:return Object.assign({}, state, {isFetching: true})case STOP_FETCH_API:return Object.assign({}, state, {isFetching: false})case RECEIVE_DATA_LIST:return Object.assign({}, state, {list: [ ...action.payload.data ]})default: return state}}
复制代码

注意

  1. 不要修改 state。 使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, { otherFilters: action.payload.data }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象

  2. 在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。

心中有疑惑

  • bindActionCreators() 是如何自动帮我把action绑定到dispatch上的?

  • 什么是纯函数?

  • 为什么reducer必须是纯函数?

  • 为什么只能通过action来修改state,直接修改有什么问题?

  • bindActionCreators() 是如何自动帮我把action绑定到dispatch上的?

  • 为什么 reducers 在 default 的情况下,一定要返回旧的state?

  • ...

未待完续

还没写完,还有下文,只是想再了解深入一点,然后继续写总结,目的是给自己,包括新手们看的,望各位大佬,手下留情,各位大佬没有兴趣可以跳过~,直接去github上看源码哈,传送门在这里 : Redux


2019.01.03 更新,下文在这 : 《为什么 redux 要返回一个新的 state 引发的血案(二)》

相关链接

  • Redux中文文档 : cn.redux.js.org/docs/basics…

  • Redux英文文档 : redux.js.org/basics/acti…

  • github : github.com/PDKSophia/b…

为什么redux要返回一个新的state引发的血案相关推荐

  1. pytorch torch.detach函数(返回一个新的`Variable`,从当前图中分离下来的)

    detach()[source] Returns a new Variable, detached from the current graph. 返回一个新的Variable,从当前图中分离下来的. ...

  2. 【字符串操作之】从原字符串中切出一段,返回一个新的字符串→→slice方法...

    slice从原字符串中切出一段,返回一个新的字符串,原字符串并未改变. 如果按 startIndex 索引到的字符与按 endIndex 索引到的字符相同或位于后者的右侧,则该方法返回一个空字符串. ...

  3. python获取字符串第一个字母_Python3基础 字符串 capitalize 返回一个新的字符串,它的第一个字母大写...

    镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体. ------------------------------------- ...

  4. php-使用函数array_walk_recursive处理多维数组的键值后返回一个新数组

    php-使用函数array_walk_recursive处理多维数组的键值后返回一个新数组 $result = array(); //$content是一个多维数组 $walk = array_wal ...

  5. java中的replace用法_java中replace用法举例:replace(char oldChar, char newChar)返回一个新的字符串...

    1.JAVA中,char占2字节,16位.可在寄存汉字 2.java中replace用法举例:replace(char oldChar, char newChar)返回一个新的字符串,它是经过用 ne ...

  6. 设计一个方法,将一个字符串中每一个英文单词的首字母大写,返回一个新的字符串

    学习后的练习内容,留个纪念~ /*** 设计一个方法,将一个字符串中每一个英文单词的首字母大写,返回一个新的字符串*/ public class NewStr {public static void ...

  7. silverlight Image Source URI : 一个反斜杠引发的血案

    silverlight Image Source URI : 一个反斜杠引发的血案 Silverlight2 现在支持的Image格式有jpg和png,部分png编码也不支持,同时有些png在xaml ...

  8. 将一个数组中重复的元素去除,并且返回一个新数组

    public static void main(String[] args) {// 声明一个数组int[] arr = {1, 7, 7, 4, 5, 1, 1, 3, 3, 6, 9};// 打印 ...

  9. 循环数组对象,并且返回一个新的数组对象

    最基础的,感觉挺好用 ```javascript [{dd:'22',AA:'张三',re1:123},{dd:'33',AA:'李四',re1:234},{dd:'44',AA:'我去',re1:4 ...

  10. React开发(226):默认方法返回一个新的参数两个括号

    <Form className={'form-customer'}><Form.Item style={{ width: '100%' }}>{getFieldDecorato ...

最新文章

  1. Git fetch和git pull的区别
  2. 利用数据寿命增加基于闪存的存储类存储器的使用寿命
  3. 计算机应用问题,计算机应用中存在的问题及解决
  4. php验证用户名密码是否为空,【后端开发】php验证用户名密码是否为空
  5. Charles 4.2.1 HTTPS抓包
  6. Java 8 - Stream实战
  7. 太难了!产品经理想拿高薪
  8. python自己的模块_Python--构建发布自己的模块
  9. JavaScript学习(四十)—字面量创建对象图解
  10. hwclock(Linux)
  11. IT 常用词汇(一)
  12. Stata15-Unicode:一次性转码解决中文乱码问题
  13. 如何做好信息化和数字化建设,看这一篇就够了--童亚斋
  14. 数字IC后端实现40天速成篇(中)
  15. 项目管理——如何制定生产计划控制生产进度
  16. HTML中让整个效果居中,仅使用CSS做到完全居中的超级攻略
  17. 使用R进行数据可视化套路之-茎叶图、盒形图
  18. 时事评论--民间只有传说
  19. 密码学领域的期刊和会议
  20. Casing of ./index.less does not match the underlying filesystem

热门文章

  1. JS 获取上传文件的内容
  2. 二:数据是用二进制数表示的
  3. [core java学习笔记][第十一章异常断言日志调试]
  4. Shell之根据关键字符串替换文件中的行
  5. bag of word C++图像批量读写
  6. ScrollMagic-jQuery元素滚动动画库插件
  7. 数据库基本----SQL语句大全(转载)
  8. Python 数据结构 树
  9. MySQL授权root
  10. 编程之美 4.7蚂蚁爬杆