react hooks 和 vue3 不使用状态管理工具实现全局响应
前言
说到状态管理工具,前端人都知道比如 vuex
、redux
,它们的主要作用就是存、读、改某些全局状态的数据,实现全局响应。不过,使用这些工具一般有个所谓最佳实践的原则,那就是能不使用,就不使用。或者说,除非不使用它的时候,某些功能无法优雅的实现,否则就最好不用。
这怎么理解呢?举个例子,我们搞一个简单点的网站全局中、英文切换,正常情况下,语言类型变量定义在全局,并能够在站内 header 组件上进行切换,那么组件结构可能是:
<div>
<header></header>
//...
</div>
此时,假设不使用全局的状态管理工具,语言类型变量数据和切换功能,维护在 header 这一层。那么不进行全局状态管理,必定需要把语言变量在很多组件中传递。
同时还有个问题,假设产品说,不仅仅需要在 header 切换语言,而且还在某一两处组件上也能切换,那么问题就更麻烦了点。
尽管如此,这个场景在业务上也并不算复杂,为了一个单薄的语言切换变量在全局响应,就多安装 vuex
包,写 store.js
,还定义 mutation
,这样好像是合理的,好像又不合理。不合理的原因就很明显了,功能单薄,框架却偏重。
React 的 createContext/useContext 和 vue3 的 provide/inject
所以,相对简单的全局响应场景下,vuex
、redux
之类的状态管理工具就显得不太合适,而合适的方式,自然就追求减负、和全局响应功能的体量相对应的。现在下面具体说说目前可以有的两种方式。
1. React hooks 的 createContext/useContext
操作方法:先在根组件比如 App.tsx
文件中定义需要全局响应的 state
,及其 setState
方法,这俩东西分别 create 一个 Context(createContext
),再在 stateContext.Provider
标签上传 state
,setState.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>
结语
- 这两种操作自我感觉很舒适,也挺推荐,在一定程度上可以替代一部分状态管理工具的能力;
- 至于react低版本,以及vue2能否实现,我尝试结果是暂不能实现的,有兴趣的兄弟可以去试一下,如果实现了,可以在评论区指点一下,贴下关键代码。此处所谓的“实现”,是类似上面两种方案,不仅在全局定义状态,并且上层的修改能响应到下层,同时下层还能修改,响应到上层。
转载自:https://segmentfault.com/a/1190000041868873