React-Redux、Recoil、useContext 做个简单的对比

关注公众号: 微信搜索 前端工具人 ; 收货更多的干货

文章来源: 自己掘金文章 https://juejin.cn/post/7073035305430810637/

React 生态丰富衍生了许多 状态管理 轮子;

开发时如何选择状态管理,也有些犯难;

在这把我所理解的、项目中用到过的(React-Redux, Recoil, useContext)做个简单的对比,仅供参考;

一、个人建议

React-Redux

  • 可以熟悉 React-Redux 的架构组件思想, 我觉得对日常开发很有用, Redux 重度患者的首选
  • 共享状态多且庞大个人觉得可以优先考虑,结合 combineReducers

Recoil

  • 日常开发完全够用,使用简单,容易上手,常用 API 也不是很多..., 挺香的
  • 状态渲染导致组件重新渲染这块, Recoil 有做优化, 性能还是很 Ok 的;

useContext

  • 函数组件开发作为局部共享状态我觉得首选吧; 对于当前页面的共享状态,也交给全局状态管理,这肯定不符合

自己日常开发

  • 全局状态管理 React-Redux 或者 Recoil(优先选择)
  • 局部状态 useContext, 二者相结合;

当然还有许多优秀的状态管理插件, MobXhox 等等,自己没怎么用就不多做讨论了。

以下介绍有不对的,或者有更好的方法,请留言,先谢谢大佬

因为并不是一直在用 react , 日常开发用的比较杂(vue3,react、flutter、electron都用)、所以见解不是很深,只是把日常开发贴出来,仅供参考

二、React-Redux

个人觉得目前来说 React-Redux 相对于其他轮子,优势没那么明显了;

由于自己是从 Redux 过来的,前期轮子并不是很多选择,所以旧项目一直使用着 React-Redux, 重构时也没替换;

缺点

  • 语法不够简洁,特别是没出 hook Api 的时候;
  • 组件间的状态共享只能通过将 state 提升至它们的公共祖先来实现,但这样做可能导致重新渲染一颗巨大的组件树

优点

React-Redux 架构组件思想我觉得非常的好, 很适用开发时对组件的一个拆分的粒度;

React-Redux 将所有组件分成两大类

  • UI 组件(presentational component
  1. 只负责 UI 的呈现,不带有任何业务逻辑
  2. 没有状态(即不使用state这个变量)
  3. 所有数据都由参数(props)提供
  4. 不使用任何 ReduxAPI
  5. 又称纯组件,即纯函数一样,纯粹由参数决定它的值, 没有任何副作用
  • 容器组件(container component
  1. 负责管理数据和业务逻辑,不负责 UI 的呈现
  2. 带有内部状态, 可使用 ReduxAPI
  3. 包含输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
  4. 输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去

UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑

React-Redux 规定所有的 UI 组件都由用户提供,容器组件则由 React-Redux自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它

如果一个组件既有 UI又有业务逻辑,那么将它拆分成:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图

代码片段

// store/index.js
// 以下类型涉及其他地方引入,就改为 any 了
import { createStore } from 'redux';

export const Types = {
  CHANGE_DEVELOP: 'CHANGE_DEVELOP',
  CHANGE_LANGUAGE: 'CHANGE_LANGUAGE'
}
const onChangeDevelop = <T = string>(val: T) => {
  return {
    type: Types.CHANGE_DEVELOP,
    payload: val
  }
}
const onChangeLanguage =  <T = string>(val: T) => {
  return {
    type: Types.CHANGE_LANGUAGE,
    payload: val
  }
}
const baseState = {
    language: 'zh',
    developer: '前端工具人'
}
interface IAction {
  type: string,
  payload: any
}
const reducer = (state = baseState, action: IAction) => {
    switch (action.type) {
        case Types.CHANGE_LANGUAGE:
          return { ...state, language: action.payload};
        case Types.CHANGE_DEVELOP:
          return { ...state, developer: action.payload };
        default:
          return state;
    }
}
const configureStore = () => createStore(reducer)
export default configureStore;

// 项目入口 App.tsx
import { Provider } from 'react-redux'
import configureStore from './store/index'
const store = configureStore()
ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
     <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

// 使用 Home.tsx
import {useDispatch, useSelector} from 'react-redux'
const Header = () => {
    const dispatch = useDispatch()
    const developer = useSelector((state) => state.developer)
    const onChangeDeveloper = ()=> { dispatch({ type:  Types.CHANGE_DEVELOP, developer: '修改后的developer' }) }
}

三、Recoil

优势

我引用的是 Recoil 文档 的介绍, 吧啦吧啦一堆 详情直戳

  • React 官方提供的提供的状态管理库, 这官方2字就很 nice, 大胆的用吧;

  • 定义了一个有向图 (directed graph),正交同时又天然连结于你的 React 树上。状态的变化从该图的顶点(我们称之为 atom)开始,流经纯函数 (我们称之为 selector) 再传入组件

  • 使用 Recoil 会为你创建一个数据流向图,从 atom(共享状态)到 selector(纯函数),再流向 React 组件。Atom 是组件可以订阅的 state 单位。selector 可以同步或异步改变此 state

  • 我们可以定义无需模板代码的 API,共享的状态拥有与 React 本地 state 一样简单的 get/set 接口 (当然如果需要,也可以使用 reducer 等进行封装)。

  • 我们有了与 Concurrent 模式及其他 React 新特性兼容的可能性。

  • 状态的定义是渐进式和分布式的,这使代码分割成为可能。

  • 无需修改对应的组件,就能将它们本地的 state 用派生数据替换。

  • 无需修改对应的组件,就能将派生数据在同步与异步间切换。

  • 我们能将导航视为头等概念,甚至可以将状态的转变编码进链接中。

  • 可以很轻松地以可回溯的方式持久化整个应用的状态,持久化的状态不会因为应用的改变而丢失

代码片段

// store/index.js
// 以下类型涉及其他地方引入,就改为 any 了
import React from 'react';
import { atom, selector, } from 'recoil';

// Recoil key 集合
const KEYS = {
    COMMON_STATE: 'commonState'
    PRODUCT_COUNT: 'productCount'
}
interface IBaseStore {
  language: string,
  developer: string,
  email: string,
  children: React.ReactNode[] | React.ReactNode
}
export const commonState = atom<IBaseStore>({
  key: KEYS.COMMON_STATE,
  default: {
    language: 'zh',
    developer: 'laisheng',
    children: [],
    email: '18826262167@163.com'
  }
})
export const productCount = selector({
  key:  KEYS.PRODUCT_COUNT,
  get: ({get}) => {
    return get(commonState)
  },
  set:({set, reset, get}, newValue) => {
   console.log(newValue)
 }
})

// App.tsx
import { RecoilRoot } from 'recoil';
const App: React.FC<AppProps> = () => {
  return (
    <RecoilRoot>
      <Router>
        <RouterPage />
      </Router>
    </RecoilRoot>
  )
}
export default App

// 使用 Home.tsx
import {useRecoilState, useRecoilValue} from 'recoil'
import { commonState } from '@/store'
const Header = () => {
    const [comState, setComState] = useRecoilState(commonState);
    setComState({ ...comState, developer: '更新工具人' })
}

四、useContext

这个作为 React-Hooks 自带的 buff 还是非常的ok

这个就没啥好介绍的了, 直接上代码片段吧

// Home.tsx
// 以下类型涉及其他地方引入,就改为 any 了
import React, { createContext, useMemo, useContext, useEffect, useReducer, useRef, useState, memo, useCallback } from 'react';

interface IHomeContext<T> {
  state: T,
  dispatch: any
}
interface IState {
  communityId: string | number,
  communityName: string,
  macAddress: string | number,
  doorControlName: string,
}
const homeState = {
  communityId: '',
  communityName: '',
  macAddress: '',
  doorControlName: ''
}
const HomeContext = createContext<IHomeContext<IState>>({
  state: homeState,
  dispatch: () => [],
})

// 类型这块这里就直接贴any了,因为要从其他地方导入类型
const onChangeHomeState = (state: any, data: any): IState => {
  for (let key in data) {
    // 避免 useContext 存储未定义的字段
    if (key in state) state[key] = data[key]
  }
  return { ...state }
}

const Home: React.FC = () => {
  const [state, dispatch] = useReducer(onChangeHomeState, homeState)
  return (
    <HomeContext.Provider value={{ state: useMemo(() => state, [state]), dispatch }}>
      <div className="home-container">
        <main>
          <Header />
          ...
        </main>
      </div>
    </HomeContext.Provider>
  )
}

// 使用 
const Header = () => {
    const { state, dispatch }  = useContext(HomeContext)
    // age、 address onChangeHomeState 并不会处理, 更新的只有 communityId
    dispatch({ 'communityId': 13, age: 18, address: '深圳' })
}
posted @ 2022-03-09 17:52  会写代码的赖先生  阅读(593)  评论(0编辑  收藏  举报