资讯专栏INFORMATION COLUMN

理解 React 轻量状态管理库 Unstated

Profeel / 452人阅读

摘要:返回,用来包裹顶层组件,向应用中注入状态管理实例,可做数据的初始化。方法返回创建的状态管理实例,作为参数传递给调用的函数,函数拿到实例,操作或显示数据。用来实现一个状态管理类。为中的状态管理实例数据。

个人网站: https://www.neroht.com

在React写应用的时候,难免遇到跨组件通信的问题。现在已经有很多的解决方案。

React本身的Context

Redux结合React-redux

Mobx结合mobx-react

React 的新的Context api本质上并不是React或者Mbox这种状态管理工具的替代品,充其量只是对React
自身状态管理短板的补充。而Redux和Mbox这两个库本身并不是为React设计的,对于一些小型的React应用
比较重。

基本概念

Unstated是基于context API,也就是使用React.createContext()来创建一个StateContext来传递状态的库

Container:状态管理类,内部使用state存储状态,通过setState实现状态的更新,api设计与React的组件基本一致。

Provider:返回Provider,用来包裹顶层组件,向应用中注入状态管理实例,可做数据的初始化。

Subscribe:本质上是Consumer,获取状态管理实例,在Container实例更新状态的时候强制更新视图。

简单的例子

我们拿最通用的计数器的例子来看unstated如何使用,先明确一下结构:Parent作为父组件包含两个子组件:Child1和Child2。
Child1展示数字,Child2操作数字的加减。然后,Parent组件的外层会包裹一个根组件。

维护状态

首先,共享状态需要有个状态管理的地方,与Redux的Reducer不同的是,Unstated是通过一个继承自Container实例:

import { Container } from "unstated";

class CounterContainer extends Container {
  constructor(initCount) {
    super(...arguments);
    this.state = {count: initCount || 0};
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }

  decrement = () => {
    this.setState({ count: this.state.count - 1 });
  }
}

export default CounterContainer

看上去是不是很熟悉?像一个React组件类。CounterContainer继承自Unstated暴露出来的Container类,利用state存储数据,setState维护状态,
并且setState与React的setState用法一致,可传入函数。返回的是一个promise。

共享状态

来看一下要显示数字的Child1组件,利用Subscribe与CounterContainer建立联系。

import React from "react"
import { Subscribe } from "unstated"
import CounterContainer from "./store/Counter"
class Child1 extends React.Component {
  render() {
    return 
      {
        counter => {
          return 
{counter.state.count}
} }
} } export default Child1

再来看一下要控制数字加减的Child2组件:

import React from "react"
import { Button } from "antd"
import { Subscribe } from "unstated"
import CounterContainer from "./store/Counter"
class Child2 extends React.Component {
  render() {
    return 
      {
        counter => {
          return 
} }
} } export default Child2

Subscribe内部返回的是StateContext.Consumer,通过to这个prop关联到CounterContainer实例,
使用renderProps模式渲染视图,Subscribe之内调用的函数的参数就是订阅的那个状态管理实例。
Child1Child2通过Subscribe订阅共同的状态管理实例CounterContainer,所以Child2可以调用
CounterContainer之内的increment和decrement方法来更新状态,而Child1会根据更新来显示数据。

看一下父组件Parent

import React from "react"
import { Provider } from "unstated"
import Child1 from "./Child1"
import Child2 from "./Child2"
import CounterContainer from "./store/Counter"

const counter = new CounterContainer(123)

class Parent extends React.Component {
  render() {
    return 
      父组件
      
      
    
  }
}

export default Parent

Provider返回的是StateContext.Provider,Parent通过Provider向组件的上下文中注入状态管理实例。
这里,可以不注入实例。不注入的话,Subscribe内部就不能拿到注入的实例去初始化数据,也就是给状态一个默认值,比如上边我给的是123。

也可以注入多个实例:


   {/*Components*}

那么,在Subscribe的时候可以拿到多个实例。


  {count1, count2) => {}
分析原理

弄明白原理之前需要先明白Unstated提供的三个API之间的关系。我根据自己的理解,画了一张图:

来梳理一下整个流程:

创建状态管理类继承自Container

生成上下文,new一个状态管理的实例,给出默认值,注入Provider

Subscribe订阅状态管理类。内部通过_createInstances方法来初始化状态管理实例并订阅该实例,具体过程如下:

从上下文中获取状态管理实例,如果获取到了,那它直接去初始化数据,如果没有获取到

那么就用to中传入的状态管理类来初始化实例。

将自身的更新视图的函数onUpdate通过订阅到状态管理实例,来实现实例内部setState的时候,调用onUpdate更新视图。

_createInstances方法返回创建的状态管理实例,作为参数传递给renderProps调用的函数,函数拿到实例,操作或显示数据。

Container

用来实现一个状态管理类。可以理解为redux中action和reducer的结合。概念相似,但实现不同。来看一下Container的源码

export class Container {
  constructor() {
    CONTAINER_DEBUG_CALLBACKS.forEach(cb => cb(this));
    this.state = null;
    this.listeners = [];
  }

  setState(updater, callback) {
    return Promise.resolve().then(() => {
      let nextState = null;
      if (typeof updater === "function") {
        nextState = updater(this.state);
      } else {
        nextState = updater;
      }

      if (nextState === null) {
        callback && callback();
      }
      // 返回一个新的state
      this.state = Object.assign({}, this.state, nextState);
      // 执行listener,也就是Subscribe的onUpdate函数,用来强制刷新视图
      const promises = this.listeners.map(listener => listener());

      return Promise.all(promises).then(() => {
        if (callback) {
          return callback();
        }
      });
    });
  }

  subscribe(fn) {
    this.listeners.push(fn);
  }

  unsubscribe(fn) {
    this.listeners = this.listeners.filter(f => f !== fn);
  }
}

Container包含了state、listeners,以及setState、subscribe、unsubscribe这三个方法。

state来存放数据,listeners是一个数组,存放更新视图的函数。

subscribe会将更新的函数(Subscribe组件内的onUpdate)放入linsteners。

setState和react的setState相似。执行时,会根据变动返回一个新的state,

同时循环listeners调用其中的更新函数。达到更新页面的效果。

unsubscribe用来取消订阅。

Provider

Provider本质上返回的是StateContext.Provider。

export function Provider(ProviderProps) {
  return (
    
      {parentMap => {
        let childMap = new Map(parentMap);

        if (props.inject) {
          props.inject.forEach(instance => {
            childMap.set(instance.constructor, instance);
          });
        }

        return (
          
            {props.children}
          
        );
      }}
    
  );
}

它自己接收一个inject属性,经过处理后,将它作为context的值传入到上下文环境中。
可以看出,传入的值为一个map,使用Container类作为键,Container类的实例作为值。
Subscribe会接收这个map,优先使用它来实例化Container类,初始化数据

可能有人注意到了Provider不是直接返回的StateContext.Provider,而是套了一层
StateContext.Consumer。这样做的目的是Provider之内还可以嵌套Provider。
内层Provider的value可以继承自外层。

Subscribe

简单来说就是连接组件与状态管理类的一座桥梁,可以想象成react-redux中connect的作用

class Subscribe extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.instances = [];
    this.unmounted = false;
  }

  componentWillUnmount() {
    this.unmounted = true;
    this.unsubscribe();
  }

  unsubscribe() {
    this.instances.forEach((container) => {
      container.unsubscribe(this.onUpdate);
    });
  }

  onUpdate = () => new Promise((resolve) => {
    if (!this.unmounted) {
      this.setState(DUMMY_STATE, resolve);
    } else {
      resolve();
    }
  })

  _createInstances(map, containers) {
    this.unsubscribe();

    if (map === null) {
      throw new Error("You must wrap your  components with a ");
    }

    const safeMap = map;
    const instances = containers.map((ContainerItem) => {
      let instance;

      if (
        typeof ContainerItem === "object" &&
        ContainerItem instanceof Container
      ) {
        instance = ContainerItem;
      } else {
        instance = safeMap.get(ContainerItem);

        if (!instance) {
          instance = new ContainerItem();
          safeMap.set(ContainerItem, instance);
        }
      }

      instance.unsubscribe(this.onUpdate);
      instance.subscribe(this.onUpdate);

      return instance;
    });

    this.instances = instances;
    return instances;
  }

  render() {
    return (
      
        {
          map => this.props.children.apply(
            null,
            this._createInstances(map, this.props.to),
          )
        }
      
    );
  }
}

这里比较重要的是_createInstances与onUpdate两个方法。StateContext.Consumer接收Provider传递过来的map,
与props接收的to一并传给_createInstances。

onUpdate:没有做什么其他事情,只是利用setState更新视图,返回一个promise。它存在的意义是在订阅的时候,
作为参数传入Container类的subscribe,扩充Container类的listeners数组,随后在Container类setState改变状态以后,
循环listeners的每一项就是这个onUpdate方法,它执行,就会更新视图。

_createInstances: map为provider中inject的状态管理实例数据。如果inject了,那么就用map来实例化数据,
否则用this.props.to的状态管理类来实例化。之后调用instance.subscribe方法(也就是Container中的subscribe),
传入自身的onUpdate,实现订阅。它存在的意义是实例化Container类并将自身的onUpdate订阅到Container类实例,
最终返回这个Container类的实例,作为this.props.children的参数并进行调用,所以在组件内部可以进行类似这样的操作:

 
   {
     counter => {
       return 
} }
总结

Unstated上手很容易,理解源码也不难。重点在于理解发布(Container类),Subscribe组件实现订阅的思路。
其API的设计贴合React的设计理念。也就是想要改变UI必须setState。另外可以不用像Redux一样写很多样板代码。

理解源码的过程中受到了下面两篇文章的启发,衷心感谢:

纯粹极简的react状态管理组件unstated

Unstated浅析

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103345.html

相关文章

  • 前端每周清单第 50 期: AngularJS and Long Term Support, Web

    摘要:在该版本发布之后,开发团队并不会继续发布新的特性,而会着眼于进行重大的错误修复。发布每六个星期,团队就会创建新的分支作为发布通道,本文即是对新近发布的版本进行简要介绍。 showImg(https://segmentfault.com/img/remote/1460000013229009); 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热...

    DobbyKim 评论0 收藏0
  • React组件设计实践总结05 - 状态管理

    摘要:要求通过要求数据变更函数使用装饰或放在函数中,目的就是让状态的变更根据可预测性单向数据流。同一份数据需要响应到多个视图,且被多个视图进行变更需要维护全局状态,并在他们变动时响应到视图数据流变得复杂,组件本身已经无法驾驭。今天是 520,这是本系列最后一篇文章,主要涵盖 React 状态管理的相关方案。 前几篇文章在掘金首发基本石沉大海, 没什么阅读量. 可能是文章篇幅太长了?掘金值太低了? ...

    ideaa 评论0 收藏0
  • [源码阅读]纯粹极简的react状态管理组件unstated

    摘要:此处继承了上面的可以注入现成的状态管理实例,添加到之中。返回值写成的意义简单一句话概括,这么写可以避免改变导致子组件的重复渲染。就是创建状态管理组件时默认传递的监听函数,用的是的更新一个空对象。返回值写成的意义。 简介 unstated是一个极简的状态管理组件 看它的简介:State so simple, it goes without saying 对比 对比redux: 更加灵活...

    FrancisSoung 评论0 收藏0
  • React 新 Context API 在前端状态管理的实践

    摘要:本文转载至今日头条技术博客众所周知,的单向数据流模式导致状态只能一级一级的由父组件传递到子组件,在大中型应用中较为繁琐不好管理,通常我们需要使用来帮助我们进行管理,然而随着的发布,新成为了新的选择。 本文转载至:今日头条技术博客showImg(https://segmentfault.com/img/bVbiNJO?w=900&h=383);众所周知,React的单向数据流模式导致状态...

    wing324 评论0 收藏0
  • 一个治愈 JavaScript 疲劳的学习计划

    摘要:只是抱怨事物的状态并没有什么卵用,我打算给你一个实实在在的一步一步征服生态圈的学习计划。好消息是,这刚好是本学习计划关注的问题。比如,一个不错的出发点是的课。是一个由创建和开源的库。我个人推荐的初学者课程。而个人项目是尝试新技术的完美时机。 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/1617原文:https://medium.fr...

    jhhfft 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<