从React的状态控制理解并总结Redux模式(上)

1.首先理解reac的工作模式:React对UI层进行了完美的抽象,我们写Web界面时几乎做到完全的去DOM化,在react中所谓组件,其实就是状态机器:在React中,我们简单的去更新某个组件的状态,然后输出基于新状态的整个界面。React负责以最高效的方式去比较两个界面并更新DOM树,也就是:对组件的管理就是对状态的管理。不同于其它框架模型,React组件很少需要暴露组件方法和外部交互。


1.reac组件的工作模式

react中组件之间共享的状态是交给组件最近的公共父节点保管,然后通过 props 把状态传递给子组件。react是通过状态提升来解决数据传递的问题。,也就是说组件之间的通信逻辑非常依赖于组件的层次结构,两个平级的组件必须要通过父节点进行中转的方式进行通信。

状态提升:当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用 props 传递数据或者函数来管理这种依赖或着影响的行为。

一旦发生了提升,就需要修改原来保存这个状态的组件的代码,也要把整个数据传递路径经过的组件都修改一遍,好让数据能够一层层地传递下去。这样对代码的组织管理维护带来很大的问题。

如何更好的管理这种被多个组件所依赖或影响的状态?

 React并没有提供好的解决方案来管理这种组件之间的共享状态。在实际项目当中状态提升并不是一个好的解决方案。

对于不会被多个组件依赖和影响的状态(例如某种下拉菜单的展开和收起状态),一般来说只需要保存在组件内部即可,不需要做提升或者特殊的管理。

2.redux的工作模式:

全局唯一的一个store去管理状态。这个store负责提供整个应用程序所有的状态,所有的组件都可以把它的状态放在这个外部的store中,当store有任何变化,组件也会自动的更新,页面上不同部分来自store上不同的节点,redux的store也是一个tree的结构,跟组件的tree结构类似一个映射的关系。

为什么要有store?:让组件通信更加容易。redux提供的解决方案:把store放在所有组件之外,所有的组件都跟这个store进行通信(单向数据流)


redux特性:

1.single source of truth:唯一的store唯一状态来源。


2. 可预测性


状态要发生变化必须是因为组件发起了action引起的。这是一个不可变数据

3.纯函数更新store。

一个称谓reducer的函数来更新store,这个函数定义了初始化statue数据,定义了如何根据action来更新state

接下来通过问题驱动去理解react-redux

1.深入浅出理解redux

redux和react-redux不是同一个东西。redux是一种架构模式(flux架构的一种变种),它不管你到底用什么库,你可以把它应用到react和vue,甚至跟jquery结合都没问题,而react-redux就是redux这种架构模式和react结合起来的一个库,就是redux架构在react上的体现。

1.react共享数据的缺点:

前面说过,通过一层一层的传props不是很好的方案,react文档提供了context来解决层级复杂的数据共享,但是也不好用,react本身也不建议使用。


react虽然不建议使用context,但是react-redux中还是用了context来实现数据共享的!!只不过react-redux把它的使用门槛提高了!!,必须遵循一套使用规则。

如上图我们很难把控每一根指向 appState 的箭头,appState 里面的东西就无法把控。react-redux规定必须通过一个“中间人” —— dispatch,所有要数据修改的动作发起必须通过它,并且必须用 action 来大声告诉它要修改什么,只有它允许的(定义好的action.type)才能修改。

react-redux解决了模块(组件)之间需要共享数据,和全局数据可能被任意修改导致不可预料的结果之间的矛盾。


redux的想法是:提高数据修改的门槛:模块(组件)之间可以共享数据,也可以改数据。但是我们约定,这个数据并不能直接改,你只能执行某些我允许的某些修改,而且你修改时必须大张旗鼓地告诉它。再也不用担心共享数据状态的修改的问题,我们只要把控了 dispatch,所有的对 appState 的修改就无所遁形,毕竟只有一根箭头指向 appState 了

functiondispatch(action) {

switch(action.type) {

case'UPDATE_TITLE_TEXT':

appState.title.text=action.text

break

case'UPDATE_TITLE_COLOR':

appState.title.color=action.color

break

default:

break

  }

}

所有对数据的操作必须通过 dispatch 函数。它接受一个参数 action,这个 action是一个普通的 JavaScript 对象,里面必须包含一个 type 字段来声明你到底想干什么。dispatch 在 swtich 里面会识别这个 type 字段,能够识别出来的操作才会执行对 appState 的修改。

2.抽离 store 和监控数据变化

有了 appState数据 和 dispatch函数, 这两个组合起来成为一个对象,这个对象命名叫store,然后就可以抽象构建一个函数 createStore,用来专门生产这种 state 和 dispatch 的组合

functioncreateStore(state,stateChanger) {

constgetState=()=>state

constdispatch=(action)=>stateChanger(state,action)

return{getState,dispatch}

}

createStore 接受两个参数,一个是表示应用程序状态的 state;另外一个是 stateChanger,它来描述应用程序状态会根据 action 发生什么变化,其实就是 dispatch。

createStore 会返回一个对象,这个对象包含两个方法 getState 和 dispatch。getState 用于获取 state 数据,其实就是简单地把 state 参数返回。

dispatch 用于修改数据,和以前一样会接受 action,然后它会把 state 和 action 一并传给 stateChanger,那么 stateChanger 就可以根据 action 来修改 state 了。

let appState={

title: {

text:'React.js 小书',

color:'red',

  },

content: {

text:'React.js 小书内容',

color:'blue'

  }

}

​functionstateChanger(state,action) {

switch(action.type) {

case'UPDATE_TITLE_TEXT':

state.title.text=action.text

break

case'UPDATE_TITLE_COLOR':

state.title.color=action.color

break

default:

break

  }

}

​conststore=createStore(appState,stateChanger)

​renderApp(store.getState())// 首次渲染页面

store.dispatch({type:'UPDATE_TITLE_TEXT',text:'《React.js 状态管理》'})// 修改标题文本

store.dispatch({type:'UPDATE_TITLE_COLOR',color:'blue'})// 修改标题颜色

renderApp(store.getState())// 把新的数据渲染到页面上

针对每个不同的 App,我们可以给 createStore 传入初始的数据 appState,和一个描述数据变化的函数 stateChanger,然后生成一个 store。需要修改数据的时候通过 store.dispatch,需要获取数据的时候通过 store.getState。

监控数据变化:

上面的代码有一个问题,每次通过 dispatch 修改数据的时候,其实只是数据发生了变化,如果我们不手动调用 renderApp,页面上的内容是不会发生变化的。但是总不能每次 dispatch 的时候都手动调用一下 renderApp,肯定希望数据变化的时候程序能够智能一点地自动重新渲染数据,而不是手动调用。

functioncreateStore(state,stateChanger) {

constlisteners=[]

constsubscribe=(listener)=>listeners.push(listener)

constgetState=()=>state

constdispatch=(action)=>{

stateChanger(state,action)

listeners.forEach((listener)=>listener())

  }

return{getState,dispatch,subscribe}

}

于是在这个 createStore 函数里面定义一个数组 listeners,定义方法 subscribe,可以通过 store.subscribe(listener) 的方式给 subscribe 传入一个监听函数,这个函数会被 push 到数组当中。

当发起dispatch时候,除了会调用 stateChanger 进行数据的修改,还会遍历 listeners 数组里面的函数,然后一个个地去调用。相当于可以通过 subscribe 传入数据变化的监听函数,每当 dispatch 的时候,监听函数就会被调用,这样就可以在每当数据变化时候自动进行重新渲染。

3.共享结构的对象提高性能

“每当数据变化时候自动进行重新渲染”这是一个很可怕的事情,功能倒是实现了,缺带来了比较严重的性能问题!!

接下来就是共享结构的对象出场啦!!

共享结构的对象

es6语法:

const obj={a:1,b:2}

const obj2={...obj}// => { a: 1, b: 2 }

const obj2 = { ...obj } 其实就是新建一个对象 obj2,然后把 obj 所有的属性都复制到 obj2 里面,相当于对象的浅复制。上面的 obj 里面的内容和 obj2 是完全一样的,但是却是两个不同的对象。除了浅复制对象,还可以覆盖、拓展对象属性:

const obj={a:1,b:2}

const obj2={...obj,b:3,c:4}// => { a: 1, b: 3, c: 4 },覆盖了 b,新增了 c

let newAppState={// 新建一个 newAppState

...appState,// 复制 appState 里面的内容

title: {// 用一个新的对象覆盖原来的 title 属性

...appState.title,// 复制原来 title 对象里面的内容

text:'《React.js 状态管理》'// 覆盖 text 属性

  }

}

树状的结构来理解对象结构


need-to-insert-img

appState 和 newAppState 其实是两个不同的对象,因为对象浅复制的缘故,其实它们里面的属性 content 指向的是同一个对象;但是因为 title 被一个新的对象覆盖了,所以它们的 title 属性指向的对象是不同的。同样地,修改 appState.title.color:

let newAppState1={// 新建一个 newAppState1

...newAppState,// 复制 newAppState1 里面的内容

title: {// 用一个新的对象覆盖原来的 title 属性

...newAppState.title,// 复制原来 title 对象里面的内容

color:"blue"// 覆盖 color 属性

  }

}

每次修改某些数据的时候,都不会碰原来的数据,而是把需要修改数据路径上的对象都 copy 一个出来。

appState!==newAppState// true,两个对象引用不同,数据变化了,重新渲染

appState.title!==newAppState.title// true,两个对象引用不同,数据变化了,重新渲染

appState.content!==appState.content// false,两个对象引用相同,数据没有变化,不需要重新渲染

修改数据的时候就把修改路径都复制一遍,但是保持其他内容不变,最后的所有对象具有某些不变共享的结构(例如上面三个对象都共享 content 对象)。大多数情况下我们可以保持 50% 以上的内容具有共享结构,这种操作具有非常优良的特性,我们可以用它来优化上面的渲染性能。

4.reducer就是一个纯函数

刚刚说到抽象了一个createStore函数,这个函数接收(state,disaptch),那么reducer就是把获取初始化数据和更新数据的功能合在一个函数里了!!

createStore 接受一个叫 reducer 的函数作为参数,这个函数规定是一个纯函数,它接受两个参数,一个是 state,一个是 action。

如果没有传入 state 或者 state 是 null,那么它就会返回一个初始化的数据。如果有传入 state 的话,就会根据 action 来“修改“数据,但其实它没有、也规定不能修改 state,而是要通过上节所说的把修改路径的对象都复制一遍,然后产生一个新的对象返回。如果它不能识别你的 action,它就不会产生新的数据,而是(在 default 内部)把 state 原封不动地返回。

reducer 是不允许有副作用的。不能在里面操作 DOM,也不能发 Ajax 请求,更不能直接修改 state,它要做的仅仅是 —— 初始化和计算新的 state

function reducer(state,action) {

if(!state)return{

themeName:'Red Theme',

themeColor:'red'

  }

switch(action.type) {

case'UPATE_THEME_NAME':

return{...state,themeName:action.themeName}

case'UPATE_THEME_COLOR':

return{...state,themeColor:action.themeColor}

default:

returnstate

  }

}

​const store=createStore(reducer)

总结:(理解react-redux)通过发现问题来驱动设计

1.在react设计中组件之间共享的状态交给组件最近的公共父节点保管,然后通过 props 把状态传递给子组件,才能实现共享数据。问题:如果我们的组件树很层次很深的话,维护起来简直是灾难

2.于是react提供了context来服务状态共享(像一个全局变量)。问题:但是共享的状态如果可以被任意修改的话,那么程序的行为将非常不可预料

3.redux其实就是提高了修改数据的门槛:你必须通过 dispatch执行某些允许的修改操作,而且必须大张旗鼓的在 action 里面声明。抽象出来一个 createStore,它可以产生 store,里面包含 getState 和 dispatch 函数,问题:每次修改完数据都需要手动重新渲染非常麻烦,希望自动重新渲染视图

4.于是给store加入了订阅者模式,可以通过 store.subscribe 订阅数据修改事件,每次数据更新的时候自动重新渲染视图。问题:“重新渲染视图”有比较严重的性能问题

5.“共享结构的对象”可解决问题,这样就可以在每个渲染函数的开头进行简单的判断避免没有被修改过的数据重新渲染。(数据对象引用相同时不会重新渲染)

对于这种模式的理解,。

定义 一个reducer,reducer 只能是纯函数,功能就是负责初始 state,和根据 state 和 action 计算具有共享结构的新的 state。

createStore 现在可以直接拿来用了,套路就是:

// 定一个 reducer

functionreducer(state,action) {

/* 初始化 state 和 switch case */

}

​// 生成 store

conststore=createStore(reducer)

​// 监听数据变化重新渲染页面

store.subscribe(()=>renderApp(store.getState()))

// 首次渲染页面

renderApp(store.getState())

// 后面可以随意 dispatch 了,页面自动更新

store.dispatch(...)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容