本文讲述如何分析设计 通过 React Hooks 进行 State 持久化管理

分析

正常前端,组件为类文件,自己维持状态,不易复用。

首先把组件中的 UI 和 状态分开,用 Action 连接,如下图。
UI=f(State).png
Action 是算子

Function

则可成为以下函数

  • UI = f(S)
    • 状态驱动组件重新渲染 UI
  • S =f(S, ∆)
    • 组件会用到的 S 和 更改 S 的 ∆ 方法决定。

S

每一个组件有他自己的状态集 s。

s


所有组件的使用到 s共同组成一个状态 S—渲染一个 UI。

s

即,收到组件影响的状态,如登录组件可能每登录一次就会增加计数器,但是对于登录组件并不会用到这个状态,虽然它会更改它。

入参

设计 State 框架时,让每一个组件声明 s状态时,提供一个更改自己的函数 m ,在 Action 事件时调用用于更改 State,而多个 m 的集合为 ∆。

S

S =f(S, ∆) 中的 S 作为 f 的参数传入,因为并不知道 Action 会更改哪些 State 【甚至不知道有哪些】,故把所有 State 都作为入参。

局部渲染

更改的状态驱动 UI 渲染,如果相同可以不改变。

如上所说,UI 由于入参为 S ,会接收所有的 State,组件自己根据自己需要的 s 变动渲染,而不是 UI 根据 S 改动分发事件。

观察 Hooks 可知,useState() 方法使用Object.is 比较算法 来比较 state。
useEffect()则提供选择让它 在只有某些值改变的时候 才执行的参数。

设计

实虚部数学模型

实数并不完备,引入虚部。

虚数,只需要去掉虚部就可以表示实数。

Curry Func 也如此。

以上同理:f(S,∆)f(S) 代表实数,不完备,加入 ∆ ,可以表达所有情况。

更改的维度从一维的 线 成为了 二维的平面。

另:框架使用的f(S, ∆)还是一维的线,但其实是该平面 任意一条线 ,因为 f(S,∆) 已经中的 ∆ 和 S 已经经由使用者确定,即在多维度选择了一个平面降维实现在代码中了。

Persistence

需要一个地方存储数据,local,session,remote 等.

Connector

组件如何把触发的事件分发给 State 处理?需要通信。

由于 js 单线程模型,选择共享内存设计新增一个 Connector 用于通信。

组件 Component 如何通知 State 改动。共享内存,采用 Connector 中间层。

Action by CurryFunc

State 如何知道框架使用者定义的 Action 改动了哪些 State ?即不知道 ∆ 的具体值。采用 Curry Func 满足延迟求值的需求。
使用 fg(S){return f(∆)} 代替 f(S,∆)

State 框架使用者自己使用 f(∆) 注册自己的状态更改算子 ∆。

State 框架开发者使用fg(S) ,只管自己传入所有的 State 即可。

由于 React Hooks 的存在,state 自带使用 f(S,∆) 进行更新的功能。故框架留出 useState() 接口,返回 f(∆),供使用者进行状态管理。

Redux

Redux 也是基于此函数模型,而在 Hooks 中官方已经使用 useReducer(reducer, initialState) 实现了它。其中 reducer 是设定好的 f(S,∆) ,而它返回 state 和 dispatch,其中 state 就是 S而 dispatch 就是 f(∆)

  1. function useReducer(reducer, initialState) {
  2. const [state, setState] = useState(initialState);
  3. function dispatch(action) {
  4. const nextState = reducer(state, action);
  5. setState(nextState);
  6. }
  7. return [state, dispatch];
  8. }

在我们看来,它也内部实现了 Connector 的作用。

实现

Persistence

首先是通过 Hooks 实现存储, 使用 Local Store

  1. function useLocalJSONStore(key, defaultValue) {
  2. const [state, setState] = useState(
  3. () => JSON.parse(localStorage.getItem(key)) || defaultValue
  4. );
  5. useEffect(() => {
  6. localStorage.setItem(key, JSON.stringify(state));
  7. }, [key, state]);
  8. return [state, setState];
  9. }

存储位置

解决了持久化存储,提供外在的状态管理支持。考虑到我们会使用 Go 来做前端:

  1. 使用 Hooks 加 sqlite3 库本地存储
  2. 使用 Hooks 和 Go 通信完成

Connector

为了使用 Hooks 实现全局的状态通知。
首先明白 useState() 获取到的 setState() 会触发当前组件的渲染:https://zh-hans.reactjs.org/docs/hooks-state.html
Connector 让使用全局状态的组件订阅来连接上全局的状态更新,将自己的 setState() 传入更新队列,当其中任何一个组件使用 dispatch() 更改状态时会触发这个命名空间下的全部状态更新,从而达到刷新所有状态组件的目的。

  1. import { useEffect } from "react"
  2. const Connector = {}
  3. const Broadcast = (name, state) => {
  4. if (!Connector[name]) return;
  5. Connector[name].forEach(setter => setter(state))
  6. }
  7. const Subscribe = (name, setter) => {
  8. if (!Connector[name]) Connector[name] =[];
  9. Connector[name].push(setter)
  10. }
  11. const UnSubscribe = (name, setter) => {
  12. if (!Connector[name]) return
  13. const index = Connector[name].indexOf(setter)
  14. if (index !== -1) Connector[name].splice(index, 1)
  15. }
  16. const connect = (name,setState) => {
  17. console.log('connect')
  18. useEffect(() =>{
  19. Subscribe(name, setState)
  20. console.log('subscirbe',name)
  21. return () => {
  22. UnSubscribe(name,setState)
  23. console.log('unsubscribe',name)
  24. }
  25. },[])
  26. }

useStore

使用者使用 useStore() 来获取全局状态和 dispatch() 函数。内部实现就是 State Hook ,并拿到 setState()注册到订阅列表中。

  1. import {Broadcast,connect} from './Connector'
  2. import {useState} from 'react'
  3. export function useStore(key,value) {
  4. const [state,setState] = useState(value)
  5. connect(key,setState)
  6. return [state, (key,value) => {
  7. Broadcast(key,value)
  8. }]
  9. }

目前状况

react-store-hook.svg

使用

使用 useStore(key, value) 即可。

  1. import {useStore} from './useStore'
  2. export function Counter({key,initialCount}) {
  3. // const [count, setCount] = useLocalJSONStore(keyname, initialCount);
  4. const [state, dispatch] = useStore(key,initialCount)
  5. return (
  6. <>
  7. Count: {state}
  8. <button onClick={() => dispatch(keyname,initialCount)}>Reset</button>
  9. <button onClick={() => dispatch(keyname,state-1)}>-</button>
  10. <button onClick={() => dispatch(keyname,state+1)}>+</button>
  11. </>
  12. );
  13. }

image.png

进阶

  • 异步状态
  • 装饰器