likes
comments
collection
share

react hooks 和 vue3 不使用状态管理工具实现全局响应

作者站长头像
站长
· 阅读数 17

前言

说到状态管理工具,前端人都知道比如 vuexredux,它们的主要作用就是存、读、改某些全局状态的数据,实现全局响应。不过,使用这些工具一般有个所谓最佳实践的原则,那就是能不使用,就不使用。或者说,除非不使用它的时候,某些功能无法优雅的实现,否则就最好不用。

这怎么理解呢?举个例子,我们搞一个简单点的网站全局中、英文切换,正常情况下,语言类型变量定义在全局,并能够在站内 header 组件上进行切换,那么组件结构可能是:

<div>
    <header></header>
    //...
</div>

此时,假设不使用全局的状态管理工具,语言类型变量数据和切换功能,维护在 header 这一层。那么不进行全局状态管理,必定需要把语言变量在很多组件中传递。

同时还有个问题,假设产品说,不仅仅需要在 header 切换语言,而且还在某一两处组件上也能切换,那么问题就更麻烦了点。

尽管如此,这个场景在业务上也并不算复杂,为了一个单薄的语言切换变量在全局响应,就多安装 vuex 包,写 store.js ,还定义 mutation,这样好像是合理的,好像又不合理。不合理的原因就很明显了,功能单薄,框架却偏重。

React 的 createContext/useContext 和 vue3 的 provide/inject

所以,相对简单的全局响应场景下,vuexredux 之类的状态管理工具就显得不太合适,而合适的方式,自然就追求减负、和全局响应功能的体量相对应的。现在下面具体说说目前可以有的两种方式。

1. React hooks 的 createContext/useContext

操作方法:先在根组件比如 App.tsx 文件中定义需要全局响应的 state,及其 setState 方法,这俩东西分别 create 一个 Context(createContext),再在 stateContext.Provider 标签上传 statesetState.Provider 标签上传 setState,在需要读写的后代组件上拿到这两个 Context,就能在后代组件上读取、修改全局数据:

//App.tsx
import React, { createContext, FC, Dispatch, SetStateAction, useState } from 'react';
import HelloWorld from './components/HelloWorld'

interface StateInf {
  name: number;
}
const defaultState: StateInf = {
  name: 111
}

export const stateContext = createContext(defaultState)
export const setStateContext = createContext<
  Dispatch<SetStateAction<StateInf>> | undefined
>(undefined)

const App: FC = () => {
  const [state, setState] = useState(defaultState)

  const changeState = () => {
    // 在根组件修改全局name变量
    setState({...state, name: 222})
  }
  return (
    <stateContext.Provider value={state}>
      <setStateContext.Provider value={setState}>
        <div>{state.name}</div>
        <button onClick={changeState}>根组件改变state</button>
        <HelloWorld />
      </setStateContext.Provider>
    </stateContext.Provider>
  )
}
export default App
// HelloWorld.tsx
import React, { useContext, FC, Dispatch, SetStateAction } from 'react';
import { stateContext, setStateContext } from '../App'

const HelloWorld: FC = () => {
  const state = useContext<{name: number}>(stateContext)
  const setState = useContext<Dispatch<SetStateAction<{name: number}>> | undefined
  >(setStateContext)

  const changeState = () => {
    if ( setState ) {
    // 此处传函数参数,实现后代组件修改全局state
      setState(state => {
        return {
          ...state,
          name: 222
        }
      })
    }
  }
  
  return (
    <div>
      <button onClick={changeState}>后代组件改变全局state值</button>
      <div>{state.name}</div>
    </div>
  )
}
export default HelloWorld

2. vue3 的 provide/inject

其实 vue3 这个方式在文档上也提到过,类似的,将需要修改的 data 及其修改方法,分别通过一个 provide 传下去,比 React 的操作要更简洁一些:

// App.vue
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import { provide, ref } from 'vue'

let data = ref(1)

const changeData = (param) => {
  // param 目前是后台组件传上来的参数,是后代组件修改的全局状态
  // 当然,这里也可以设置其他数据结构,全局也传个修改的值
  if ( param ) {
    data.value = param
  } else {
    data.value = 2
  }
}

provide('changeData', changeData)
provide('data', data)
</script>

<template>
  <div>{{data}}</div>
  <button @click="changeData('')">在根组件上修改全局 data</button>
  <div>
    <HelloWorld />
  </div>
</template>
// HelloWorld.vue
<script setup>
import { inject } from 'vue'
  
const changeData = inject('changeData')
const d = inject('data')
</script>

<template>
  <button @click="changeData(33)" style="cursor: pointer">子组件改变上层context值 </button><br />
  <span>{{d}}</span>
</template>

结语

  1. 这两种操作自我感觉很舒适,也挺推荐,在一定程度上可以替代一部分状态管理工具的能力;
  2. 至于react低版本,以及vue2能否实现,我尝试结果是暂不能实现的,有兴趣的兄弟可以去试一下,如果实现了,可以在评论区指点一下,贴下关键代码。此处所谓的“实现”,是类似上面两种方案,不仅在全局定义状态,并且上层的修改能响应到下层,同时下层还能修改,响应到上层。