关于javascript:精读一种-Hooks-数据流管理方案

52次阅读

共计 4595 个字符,预计需要花费 12 分钟才能阅读完成。

保护大型项目 OR UI 组件模块时,肯定会遇到全局数据传递问题。

保护我的项目时,像全局用户信息、全局我的项目配置、全局性能配置等等,都是跨模块复用的全局数据。

保护 UI 组件时,调用组件的入口只有一个,但组件外部会持续拆模块,分文件,对于这些组件内模块而言,入口文件的参数也就是全局数据。

这时个别有三种计划:

  1. props 透传。
  2. 上下文。
  3. 全局数据流。

props 透传计划,因为任何一个节点掉链子都会导致参数传递失败,因而带来的保护老本与心智累赘都特地大。

上下文即 useContext 利用上下文共享全局数据,带来的问题是更新粒度太粗,同上下文中任何值的扭转都会导致重渲染。有一种较为 Hack 的解决方案 use-context-selector,不过这个和上面说到的全局数据流很像。

全局数据流即利用 react-redux 等工具,绕过 React 更新机制进行全局数据传递的计划,这种计划较好解决了我的项目问题,但很少有组件会应用。以前也有过不少利用 Redux 做部分数据流的计划,但实质上还是全局数据流。当初 react-redux 反对了部分作用域计划:

import {shallowEqual, createSelectorHook, createStoreHook} from 'react-redux'

const context = React.createContext(null)
const useStore = createStoreHook(context)
const useSelector = createSelectorHook(context)
const useDispatch = createDispatchHook(context)

因而是机会好好梳理一下数据流治理计划,做一个我的项目、组件通用的数据流治理计划。

精读

对我的项目、组件来说,数据流蕴含两种数据:

  1. 可变数据。
  2. 不可变数据。

对我的项目来说,可变数据的起源有:

  1. 全局内部参数。
  2. 全局我的项目自定义变量。

不可变数据起源有:

  1. 操作数据或行为的函数办法。

全局内部参数指不受我的项目代码管制的,比方登陆用户信息数据。全局我的项目自定义变量是由我的项目代码管制的,比方定义了一些模型数据、状态数据。

对组件来说,可变数据的起源有:

  1. 组件被调用时的传参。
  2. 全局组件自定义变量。

不可变数据起源有:

  1. 组件被调用时的传参。
  2. 操作数据或行为的函数办法。

对组件来说,被调用时的传参既可能是可变数据,也可能是不可变数据。比方传入的 props.color 可能就是可变数据,而 props.defaultValueprops.onChange 就是不可变数据。

当梳理分明我的项目与组件到底有哪些全局数据后,咱们就能够依照注册与调用这两步来设计数据流治理标准了。

数据流调用

首先来看调用。为了同时保障应用的便捷与应用程序的性能,咱们心愿应用一个对立的 API useXXX 来拜访所有全局数据与办法,并满足:

  1. {} = useXXX() 只能援用到不可变数据,包含变量与办法。
  2. {value} = useXXX(state => ({ value: state.value})) 能够援用到可变数据,但必须通过选择器来调用。

比方一个利用叫 gaea,那么 useGaea 就是对这个利用全局数据的惟一调用入口,我能够在组件里这么调用数据与办法:

const Panel = () => {
  // appId 是利用不可变数据,所以即便是变量也能够间接获取,因为它不会变动,也不会导致重渲染
  // fetchData 是取数函数,内置发送了 appId,所以绑定了肯定上下文,也属于不可变数据
  const {appId, fetchData} = useGaea()

  // 主题色可能在运行时批改,只能通过选择器获取
  // 此时这个组件会额定在 color 变动时重渲染
  const {color} = useGaea(state => ({color: state.theme?.color}))
}

比方一个组件叫 Menu,那么 useMenu 就是这个组件的全局数据调用入口,能够这么应用:

// SubMenu 是 Menu 组件的子组件,能够间接应用 useMenu
const SubMenu = () => {
  // defaultValue 是一次性值,所以解决时做了不可变解决,这里曾经是不可变数据了
  // onMenuClick 是回调函数,不论传参援用如何变动,这里都解决成不可变的援用
  const {defaultValue, onMenuClick} = useMenu()

  // disabled 是 menu 的参数,须要在变动时立刻响应,所以是可变数据
  const {disabled} = useMenu(state => ({disabled: state.disabled}))

  // selectedMenu 是 Menu 组件的外部状态,也作为可变数据调用
  const {selectedMenu} = useMenu(state => ({selectedMenu: state.selectedMenu}))
}

能够发现,在整个利用或者组件的应用 Scope 中,曾经做了一层形象,即不关怀数据是怎么来的,只关怀数据是否可变。这样对于组件或利用,随时能够将外部状态凋谢到 API 层,而外部代码齐全不必批改。

数据流注册

数据流注册的时候,咱们只有定义三种参数:

  1. dynamicValue: 动静参数,通过 useInput(state => state.xxx) 能力拜访到。
  2. staticValue: 动态参数,援用永远不会扭转,能够间接通过 useInput().xxx 拜访到。
  3. 自定义 hooks,入参是 staticValue getState setState,这里能够封装自定义办法,并且定义的办法都必须是动态的,能够间接通过 useInput().xxx 拜访到。
const {useState: useInput, Provider} = createHookStore<{
  dynamicValue: {fontSize: number}
  staticValue: {onChange: (value: number) => void
  }
}>(({staticValue}) => {const onCustomChange = React.useCallback((value: number) => {staticValue.onChange(value + 1)
  }, [staticValue])

  return React.useMemo(() => ({onCustomChange}), [onCustomChange])
})

下面的办法裸露了 ProvideruseInput 两个对象,咱们首先须要在组件里给它传输数据。比方我写的是组件 Input,就能够这么调用:

function Input({onChange, fontSize}) {
  return (<Provider dynamicValue={{fontSize}} staticValue={{onChange}}>
      <InputComponent />
    </Provider>
  )
}

如果对于某些动态数据,咱们只想赋初值,能够应用 defaultDynamicValue

function Input({onChange, fontSize}) {
  return (<Provider dynamicValue={{fontSize}} defaultDynamicValue={{count: 1}}>
      <InputComponent />
    </Provider>
  )
}

这样 count 就是一个动静值,必须通过 useInput(state => ({ count: state.count})) 能力取到,但又不会因为外层组件 Rerender 而被从新赋值为 1。所有动静值都能够通过 setState 来批改,这个前面再说。

这样所有 Input 下的子组件就能够通过 useInput 拜访到全局数据流的数据啦,咱们有三种拜访数据的场景。

一:拜访传给 Input 组件的 onChange

因为 onChange 是不可变对象,因而能够通过如下形式拜访:

function InputComponent() {const { onChange} = useInput()}

二:拜访咱们自定义的全局 Hooks 函数 onCustomChange

function InputComponent() {const { onCustomChange} = useInput()}

三:拜访可能变动的数据 fontSize。因为咱们须要在 fontSize 变动时让组件重渲染,又不想让下面两种调用形式受到 fontSize 的影响,须要通过如下形式拜访:

function InputComponent() {const { fontSize} = useInput(state => ({fontSize: state.fontSize}))
}

最初在自定义办法中,如果咱们想批改可变数据,都要通过 updateStore 封装好并裸露给内部,而不能间接调用。具体形式是这样的,举个例子,假如咱们须要定义一个利用状态 status,其可选值为 editpreview,那么能够这么去定义:

const {useState: useInput, Provider} = createHookStore<{
  dynamicValue: {
    isAdmin: boolean
    status: 'edit' | 'preview'
  }
}>(({getState, setState}) => {const toggleStatus = React.useCallback(() => {
    // 管理员能力切换利用状态
    if (!getState().isAdmin) {return}

    setState(state => ({
      ...state,
      status: state.status === 'edit' ? 'preview' : 'edit'
    }))
  }, [getState, setState])

  return React.useMemo(() => ({toggleStatus}), [toggleStatus])
})

上面是调用:

function InputComponent() {const { toggleStatus} = useInput()

  return (<button onClick={toggleStatus} />
  )
}

而且整个链路的类型定义也是齐全主动推导的,这套数据流治理计划到这里就讲完了。

总结

对全局数据的应用,最不便的就是收拢到一个 useXXX API,并且还能辨别动态、动静值,并在拜访动态值时齐全不会导致重渲染。

而之所以动静值 dynamicValue 须要在 Provider 里定义,是因为当动静值变动时,会自动更新数据流中的数据,使整个利用数据与内部动态数据同步。而这个更新步骤就是通过 Redux Store 来实现的。

本文特意没有给出实现源码,感兴趣的同学能够本人实现一个试一试。

探讨地址是:精读《一种 Hooks 数据流治理计划》· Issue #345 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。

关注 前端精读微信公众号

<img width=200 src=”https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg”>

版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)

正文完
 0