React的状态管理

主要内容,看看State的状态管理方式,包括最基本的方式和React-Hooks方式以及Redux方式和ReSub方式 咱们从基本的方式开始javascript

React和数据的基本交互方式

在MVC程序构架中,React常常被称为View层,但实际上并不彻底是这样, React实际对MVC模式作了新的构想. 本质上React只是借助JSX语法实现的UI界面库,可是UI都须要数据来填充,因此问题就是如何获取数据,如何灵活的展示数据.java

MVC的思想

MVC架构的基本思想:react

  • 模型层(Model)就是数据层.
  • 视图层(View)负责整个应用程序的展现.
  • 控制层(Controller)在应用程序中扶着提供数据处理的逻辑操做.

React处理数据和MVC有微妙的区别. 在由多个子组件组合而成的视图(父组件)里, 子组件能够管理本身的数据处理方式,并且也能够从父组件获取数据,只须要在父组件中提供一个控制器就能够了.git

React的思想

在React的组件中有两种不一样的数据类型:程序员

  • props ,在建立组件的时候,props会做为参数传递给组件,这个就做为组件顶级的配置项,一旦定义好,组件就不能自行修改了. 在React定的父组件->子组件的信息传递中,只能使用这一种方式.没有其余的方法. Props是React组件库的精华, 咱们能够定义不一样的Props来控制组件的表现形式.github

  • state,state是组件内部的数据.React的精华实际就在state上,咱们能够在父组件中定义一个state,而后以Props的形式传递给子组件, state只是一个JS对象,咱们能够定义任何形式的属性. state的定义多样性,决定了你的应用的多样性. 经过定义组件的state,能够实现基本的状态管理,也能够实现类Redux管理方式, 还能够实现React-Hooks的管理方式. 若是深刻一点,你须要知道,Redux其实就是一个只有State逻辑处理而没有UI的React组件.编程

    进行State修改的方法就只有一个 this.setState({}).在Redux这个特殊的React组件中,也是经过这个方法来修改App的State,只不过咱们看不到实现细节. 后续我会经过一个表单来看看看里面具体的实现.redux

    以上内容整理自构建 F8 2016 App的介绍浏览器

基本实现

State设计是React应用最重要的部分.这个设计,我认为也是React思想创建的关键. 核心是如何思考State的提高, 也就是不断的把State提高到更高一级的组件中. 可是这个提高也要适可而止, 应该老是以具体的处理流程做为分割线. 同一个流程的State,最终能够提高为一个总的State,例如和登陆,注册,登出,找回密码和修改密码的流程,就能够提高为一个大的State. 不相关流程的State,就不要混合在一块儿.无论你是使用基础的State管理,Redux管理,Hooks管理,包括ReSub,这一点都是同样的.bash

React文档中的基本处理方法

单个字段的‌表单

class SingleFieldForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
复制代码

这是一个简单的表单组件,要实现这个表单,不只要使用state,还有props,同时还要有展现内容的UI组件

在表单组件中定义了state:

//只是一个JS对象,属性名为value
this.state = {value: ''};
复制代码

定义了处理state的方法:

handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

复制代码

在建立一个表单组件的时候,须要反馈给输入用户到底本身输入的是什么,还有如何进行表单提交的方法.上面两段代码就定义这两个内容. 那么表单组件内部的子组件直接获取输入的内容和提交方法就能够了.从父组件向子组件传递数据时,咱们就须要用到props.就是下面的代码

<form onSubmit={this.handleSubmit}>
        <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> 复制代码

这里的form,input[type="text"],input[type="submit"] 都是props的用法. 这里只要牢记一点, 在return中出现的全部参数都是props, render以外的是state,

render(){
 return(
    ...code
 )
 
复制代码

在JS中,咱们是传引用赋值的,因此在子组件就能够经过引用的方法名来操做父组件定义的State, 那么这里就有一个问题, 若是咱们继续把父组件中定义的State和State处理方法提高的爷爷组件,在继续提高的太爷爷组件上,应该是同样的吧? 我能够确切的说, React的代码编写就是这个原则. 只不过state的设计须要稍稍复杂一点.

若是是多个字段的表单,咱们应该如何编写代码?

若是按照常规是这样的 ‌多字段表单常规写法

class ThreeFieldsForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {name: ''
                  age: null,
                  email:''
     };

    this.handleNameChange = this.handleNameChange.bind(this);
    this.handleAgeChange = this.handleAgeChange.bind(this);
    this.handleEmailChange = this.handleEmailChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleNameChange(event) {
    this.setState({name: event.target.value});
  }
  
  handleAgeChange(event) {
    this.setState({age: event.target.value});
  }
  handleEmailChange(event) {
    this.setState({email: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.name} onChange={(event)=>this.handleNameChange(event.target.value)} />
        </label>
        <label>
           Age:
          <input type="text" value={this.state.age} onChange={this.handleAgeChange} />
        </label>

<label>
          Email:
          <input type="text" value={this.state.email} onChange={this.handleEmailChange} />
        </label>

        <input type="submit" value="Submit" />
      </form>
    );
  }
}
复制代码

这里是三个字段的表单, 若是是十个字段, 那么state和处理方法代码就太多了, 而且你发现这些代码只有一个地方是不一样,或许咱们能够在state处理方法上想一想办法?

‌把handleChange方法抽象出来

class ThreeFieldsForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {name: ''
                  age: null,
                  email:''
     };

       
    
    
  }
  //这里用了ES6的箭头函数就不须要再绑定啦
  setValue = (text, type) => {
    switch (type) {
      case "setName":
        this.setState({ name: text });
        break;
      case "setAge":
        this.setState({ age: text });
        break;
      case "setEmail":
        this.setState({ email: text });
        break;
      
    }
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" 
             type="setName"
            onChange={(event)=>this.setValue(event.target.value,"setName")}
            value={this.state.name} />
        </label>
        <label>
           Age:
          <input type="text" 
          type="setAge"
           onChange={(event)=>this.setValue(event.target.value,"setAge")}
          value={this.state.age} />
        </label>

<label>
          Email:
          <input type="text" 
          type="setEmail"
          onChange={(event)=>this.setValue(event.target.value,"setEmail")}
          value={this.state.age} />
        </label>

        <input type="submit" value="Submit" />
      </form>
    );
  }
}
复制代码

这里有两个词,若是你看了Redux和React-Hooks,可能会以为很眼熟, 一个是type,另外一个是setValue, 没错这个地方也是我写这篇文章的着眼点,上周我想到这个地方的的时候,就以为常规的State处理,Redux和React-hooks对于State的处理其实并无明确的界限. 如何使用就是React程序员须要考虑的问题.

若是这个表单用React-Hooks处理是这个样子的

import {useState}  from 'React';

 const  ThreeFieldForm=(props)=>{
    const [name,setName]=useState("");
    const [age,setAge]=useState(null);
    const  [email,setEmail]=useState("")
  return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" 
             type="setName"
            onChange={(event)=>setName(event.target.value)}
            value={this.state.name} />
        </label>
        <label>
           Age:
          <input type="text" 
          type="setAge"
           onChange={(event)=>setAge(event.target.value)}
          value={this.state.age} />
        </label>

<label>
          Email:
          <input type="text" 
          type="setEmail"
          onChange={(event)=>this.setEmail(event.target.value)}
          value={this.state.age} />
        </label>

        <input type="submit" value="Submit" />
      </form>
    );
}

复制代码

上面这三个Hooks能够继续抽象为useForm的形式, 为对象添加type,结合JS的闭包,不少问题变简洁了.使用Hooks,并返回新的对象和方法也是使用Hooks的一个模式,具体的能够看看youtube上的一些视频. 若是咱们在处理的方法中加了type那就能够用useReducer啦! useReducer能够看下面这段代码.

useReducer

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "reset":
      return initialState;
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
  }
}

function Demo() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </>
  );
}
复制代码

这里的这段代码,咱们先不做解释,若是对Redux比较理解了, useReducer的方法也是比较好理解的. 仍是以前提到的那一句话, state的管理方法不是绝对的, 看看如何思考具体的是用. 通过以前的提高操做, 若是更进一步,把全部的state都提高到一个顶级的组件中, Redux模式就完成了. 这一点我在后面会继续讲到, 其实在不少讲解Redux的图示中,都会提到数据单向流动, 没错一旦全部的State都提高到顶级的组件中, 数据就只能经过props的形式传递给子组件. 最大的子组件就是Redux的包装下的那个App组件.

若是你看过Redux的模型图,例以下面这一张:

或者我本身画的

数据是单向流动的,从React-Redux组件流向应用的组件. 可是在第一张图的右侧的Actions彷佛有流了回去, 这算是单向流动吗? 这个问题时间用dispatch并很差理解, 用ReSub的触发彷佛要好一点. 后边我会结合一个本身想的现实生活中的实例来解释这个问题.

下面我要声明我本身的一个学习体会, React通过几年的高速发展, 构架不断的向函数式编程方向发展, 函数式组件内部的JSX代码结合传入的数据,咱们想要的应用就实现了. 因此归结为两点一个是函数式组件,另一个就是数据. 在React中流动的数据仅仅只是JS对象,若是咱们给这些对象添加了定义好了Type类型,那么数据就能够井井有理的呗管理和组织,就是这么简洁,注意是简洁并非简单, 要想设计好State也不是一件容易的事情.

来自flutter文档里的图,和React的原理是彻底相同的

上面这张图了来自flutter文档,就是我要表达的意思 下面咱们要进入本文的主题了, 通俗的学习Redux.

React的状态管理的权威-Redux

这里我不想很正式的讲解Redux,Redux文档写的很是好,可能一开始看以为很难,可是看过十几遍以后,你会以为甘之如饴. 没有看十遍以上的,是苦的. 因此我想换个方法, 用通俗的方法来解释一下,这个问题.做为看文档的补充.不少时候看问题要换个角度,或者换个容易理解的模型就比较容易理解了.

Redux的通俗理解

咱们就从 这张图的Store开始

这一段时间我都在思考Redux Store的通俗理解方法. 结果发现自己这个单词就是最好的诠释.

这里的Store我想用两个模型解释,一个是沃尔玛的Super Store,一个是电商的Store,就拿JD商城作例子吧.

从Store开始.

沃尔玛的Store

若是你没去过沃尔玛,把沃尔玛换成全家便利店也能够, 规模不一样,结构和组织彻底相同. 可是若是类比Redux的Store,大型超市的多人管理更相似些

在Store里,首先你会看大不少的货架,一个Store在刚开始初始化的时候是这个样子的

Store初始化的货架

开张的时候是这个样子的:

Store的货架摆满货物

基本大型超市会分红不一样的楼层,而后分红不一样的区域,不一样的货架 处理具体货架的人是不一样的,因此尽管很大,可是因为进行了分区,分层处理,管理是层次分明的.每一个区域,每一个分类,每一个货架都有不一样的标签来标识. 每种货物的具体补货,出货,换货等方法都相应的不一样, 可是只要找到具体每一个区的负责员工就能够实现了. 看这个天天超市庞大的货物吞吐量, 其实进入到超市以后,就像看不见的洋流同样实际上是在各自区域中独立的流动. 看到这里你有没有具体的代码结构,文件夹结构如何安排? 我想按照货品的不一样分类主导代码的结构是比较很好的. 能够先看看gitpoint的代码,

.gitpoint的代码就是按照不一样的"洋流"来安排的. 后面咱们再谈这个问题. 与之对照, 在Redux中全部的应用State,初看起来也是很是庞大,可是具体到实现,都由JS对象的键来区分和管理,各自也包含了本身的State处理方法. 每一个小部分的对象和处理方法就统称为 reducer,每一个小分区的State又经过 CombineReducer组合成最大的Reducer,咱们能够从整个Reducer里获取到应用的完整State. 咱们去超市,抽象的是和超市打交道,具体的是和每一个终端在员工和货架在打交道. 因此尽管超市很大,可是处理问题的方式却很简单.

京东的Store

京东的Store,和咱们React里的Store就很是接近了. 之因此拿电商来作实际的例子,要解决几个问题,一是如何理解Redux的 dispatch方法,另外一个是如何理解connect. 这里我先作一个通俗的解释,而后讲解一张我认为对这个模式解释最好的图片.还有就是数据的不可突变性. 前面咱们提到了沃尔玛超市的货架,分区. 那么和这里的电商的Store有什么区别? 差异就是咱们浏览器或者是手机app看到的分类是虚拟的, 可是实际效果和实体超市同样,在处理虚拟的Store数据时,也要可以按照分区,分类的方法来管理.这也就是Redux中Store的管理方式.

dispatch

dispatch时,到底有没有数据从用户流向Store? 这个单词翻译为中文叫分派,彷佛还不太准确,准确的翻译应该叫触发. ReSub这个库就用了trigger这个词. 最好的处理就是把state和处理state的方法统一放大一个地方. 因为JS是传引用赋值的,咱们能够把修改State的方法经过props的形式传递给子组件, 子组件只须要触发对应的操做就能够了.因此这里用触发的解释比较好. 面对一个庞大的电商Store,也没有什么担忧的,只要定义好了不会引起歧义的type就能够了. 咱们触发一个操做,就是执行一个Store定义的方法,根据触发的type对Redux的State作出修改.

咱们在京东购物时,点击购买,提交的就是商品品名,数量,此外咱们还要提供本身的地址,至关于为本身的地址绑定了此次购物,等物品从JD的Store出来以后,后按照你的地址进行派送. 整个流程几乎是彻底相同的.

connect

从JD Store出来的货物是针对所有买家的,不是每件商品都是你须要的. 因此当Store的货物返回到社会之后,须要根据买家的地址来进行筛选和分类,而后由快递员按你提供的地址进行派送. 这个过程是自动, 你不须要本身动手, 由于以前已经进行了订阅.

用Redux的方法就是使用mapStateToProps把某个组件须要的数据筛选出来. 因为须要dispatch的Store方法也是从外部传递的,全部就有了mapDispatchToProps方法, 传递Store对应的方法名. 在重复一下, 组件外部的数据只能经过props传递.

好了时候借用别人的杀手锏了. 下面这张图嘛,你能够想象是你有几个朋友,分别在不一样的城市,你用他们的地址进行了订阅,而后你在JD上提交了订单,触发了JD Store的一次操做,而后JD根据你的订阅地址把货物发送到几个朋友手中.

图片出处 when-do-i-know-im-ready-for-redux

你如今能够进入这张图中,你的家就在最右边的这个球中,你触发了一个订购操做,好比 在2019年4月22号20点20分20秒195毫秒时 订购了三只中华铅笔,HB的. 而后JD的的文具分部接受根据你触发的动做的类型作了相应的处理,通知Store出货,而后铅笔库存减掉3, 这时若是还有其余人想买中华的HB铅笔,就会显示无货. 你的此次订阅和小米的旗舰店没有任何的关联, 尽管从外面看JD的铅笔和小米的手机是从同一个地方出来的,可是在Store内部是由不一样的分支来处理的.

上面我专门添加了一个时间,是为了要解决数据的不可变性这个问题,就是在Redux文档中提到的时间旅行的问题.

数据的不可突变性对于JavaScript是一个问题,可是对于某些语言就不是问题. JS中的这个问题是因为JS对于数据存储的方法引发的.

例如咱们要在JS中定义一个蜡笔的颜色为红色:

定义一个红色蜡笔颜色

而后咱们把对象变为蓝色的对象, JS会为这个对象从新分配内存地址

修改蜡笔为蓝色颜色对象

可是若是咱们只修改对象的属性,问题就来了,JS会在原位置对对象做出修改

修改蜡笔的颜色属性

因为Redux Store中的state是嵌套对象, 若是对某一部分属性进行修改, 内存地址不会发生改变, Store可能认为你没有作什么修改工做,由于在Store中使用'==='来决定state是否发生改变的.===符号在JS中就是比较对象的内存地址的. 因此在Redux中须要手动把要修改的State复制到新的内存地址中,而后在作修改,从而让Store能够觉察到State的变化.

以上解释来自[Immutability in React and Redux: The Complete Guide](https://daveceddia.com/react-redux-immutability-guide/). 若是理解有误差,敬请指出

可是这样作每次修改都要开辟新的内存地址, 是比较浪费内存的.因此FaceBook提出了 Immutable.js 的方法. 这就是我上面用到的那个时间段的意思. 仍是在JD的Store, 咱们要出货,管理库存,当用户订购了三只中华铅笔,库存要减掉, 咱们能够把全部的库存帐本从新抄一遍,而后把中华铅笔的库存减掉3.可是实际的库存管理不是这样作的, 咱们有一个总的库存目录, 而后单独在一个地方记载某个时间某个商品的库存发生了什么变化, 没有变化的部分,就无论了. 这就是Immutable的处理方法. 记载变化的位置,共享不变的位置. 若是咱们不为修改打上时间戳就没有办法知道历史记录了,由于历史数据被新的数据给替换掉了. 因此实际的帐目中不只要记录帐目发生变化的品名还要记录时间. Redux的时间旅行就是这个意思.

上面的那篇文章对于JS的Immutability操做解释的很是好, 我也准备翻译. 尤为是后面的Immer库很方便.

微软的Resub

下面咱们来看看微软的Resub库. 这个库是配合微软的ReactXP项目的附属. 我没看过mobx的文档,我猜测应该和Mobx是很像的.

主要内容就是使用StoreBase定义数据和数据处理方法,

import { StoreBase, AutoSubscribeStore, autoSubscribe } from 'resub';

@AutoSubscribeStore
class TodosStore extends StoreBase {
    private _todos: string[] = [];

    addTodo(todo: string) {
        // Don't use .push here, we need a new array since the old _todos array was passed to the component by reference value
        this._todos = this._todos.concat(todo);
        this.trigger();
    }

    @autoSubscribe
    getTodos() {
        return this._todos;
    }
}

export = new TodosStore();
复制代码

在组件中使用数据和方法

import * as React from 'react';
import { ComponentBase } from 'resub';

import TodosStore = require('./TodosStore');

interface TodoListState {
    todos?: string[];
}

class TodoList extends ComponentBase<{}, TodoListState> {
    protected _buildState(props: {}, initialBuild: boolean): TodoListState {
        return {
            todos: TodosStore.getTodos()
        }
    }

    render() {
        return (
            <ul className="todos">
                { this.state.todos.map(todo => <li>{ todo }</li> ) }
            </ul>
        );
    }
}

export = TodoList;
复制代码

应该也算是很是简洁的.并且有TS的类型约束, 出错的机会要少不少. Redux的TS方法,我后面也会提到.

未完成,还有一些内容

相关文章
相关标签/搜索