你仍是在用Redux来管理组件状态吗?

前言

为何不用Redux

一句话归纳就是使用Redux太麻烦了:css

  • 比较适合中大型项目
  • 添加一次全局状态的过程及其麻烦且容易出错
  • Redux状态树变多时,维护更加困难

React Hook了解一下

自从React发布了React Hook后,你们就想到了使用useContextuseReducer来模拟Reduxhtml

好处是什么:react

  • 能够和Redux分离使用,只需在用到的地方添加状态,至关于一个局部的全局状态。
  • 适合小型项目或我的学习React时开发
  • 不用写过多的代码,易于后续维护

先分别介绍一下useContextuseReducer分别是作什么的:git

useContext

要使用useContext以前,咱们须要先知道Context是什么,官方文档里面有说明github

Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。typescript

也就是说,使用Context来管理咱们的“全局”状态是再好不过的了。bash

useContext就是为了接收context对象并返回context的值而存在的。说白了就是咱们能够经过useContext来访问全局状态Contextapp

useReducer

咱们先来看一下useReducer的函数。ide

const [state, dispatch] = useReducer(reducer, initialArg, init);
复制代码

它接收一个如(state, action) => newStatereducer,并返回当前的state以及预期配套的dispath方法。(用过Redux 应该不会陌生)官方文档函数

开始实践

下面咱们用一个简单的例子经过useContextuseReducer来实现Redux的效果。

该例子使用实现主题色切换来管理全局状态

初始化项目

你能够经过create-react-app建立一个React项目进行测试,也能够在CodeSandbox进行模拟

笔者用的是TypeScript进行项目编写的,你也能够经过

npx create-react-app my-app(项目名) --typescript
# 或者
yarn create react-app my-app(项目名) --typescript
复制代码

建立一个TypeScriptReact项目

建立两个子组件

建立好项目后,在src目录下新建一个pages

pages内分别建立switchtheme两个组件,这里只列出主要目录结构

.
├── src
|   └── pages
|   |   ├── switch
|   |   |   └── index.tsx   // 用来修改theme状态
|   |   └── state
|   |   |   └── index.tsx   // 用来显示theme状态
└── store
    └── theme.tsx           // 用来存储theme状态
复制代码

建立State组件(用来显示theme状态)

index.tsx

import React from 'react'

const State: React.FC = () => {
    return (
        <div>由我显示全局变量</div>
    )
}

export default State;
复制代码

建立Switch组件(用来修改theme状态)

index.tsx

import React from 'react'

const Switch: React.FC = () => {
    return (
        <section>
            <button>我是改变状态1</button>
            <button>我是改变状态2</button>
        </section>
    )
}

export default Switch;
复制代码

如今咱们把这两个组件都引入到App.tsx

import React from 'react';
import Switch from './pages/switch'
import State from './pages/state'
import './App.css';

const App: React.FC = () => {
  return (
    <div className="App">
      <Switch></Switch>
      <State></State>
    </div>
  );
}

export default App;
复制代码

此时咱们运行咱们的项目,能够看到页面大概是这样的

初始状态

此时咱们第一步就算完成了

状态管理

src目录下建立一个store目录文件,新建一个文件,名为theme.tsx

theme.tsx

初始化主题值

import React, { createContext, Context } from 'react'
// 定义主题色的接口
interface ITheme {
    theme: string
}
// 初始化
export const initialTheme: ITheme = {
    theme: '等待改变主题'
}
// 建立一个Context实例
export const ThemeContext: Context<any> = createContext(initialTheme);

/**
 * 建立一个 Theme 组件
 * Theme 组件包裹的全部子组件均可以经过调用 ThemeContext 访问到 value
 */
export const Theme: React.FC = (props) => {
    return (
        <ThemeContext.Provider value={{state: initialTheme}}>
            {props.children}
        </ThemeContext.Provider>
    )
}
复制代码

这里有个重点就是,当ThemeContext.Providervalue值发生变化时,它内部的全部子组件都会从新渲染

因此咱们须要将Theme组件包裹在StateSwitch的外层

此时App.tsx变为

import React from 'react';
import Switch from './pages/switch'
import State from './pages/state'
import { Theme } from './store/theme'
import './App.css';

const App: React.FC = () => {
  return (
    <div className="App">
      <Theme>
        <Switch></Switch>
        <State></State>
      </Theme>
    </div>
  );
}

export default App;
复制代码

此时咱们第二步就算完成了

State读取状态(useContext)

如今咱们能够在State组件内经过useContext来读取Theme的状态了

// 引入useContext
import React, { useContext } from 'react'
// 引入Theme的Context
import { ThemeContext } from '../../store/theme'

const State: React.FC = () => {
    // 获取Theme传来的value值
    const { state } = useContext(ThemeContext)

    return (
        <div className="theme light">{state.theme}</div>
    )
}

export default State;
复制代码

此时运行咱们的项目,发现State已经读取到了Theme的值,说明咱们的第三步已经成功了

获取Theme状态成功

改变状态(useReducer)

接下来咱们须要经过Switch内的两个按钮来改变Theme的值,看下State组件内的状态有没发生变化

在Theme内添加reducer

Theme组件内使用useReducer而且添加reducer方法用于修改theme的状态

import React, { createContext, Context, useReducer } from 'react'
// 定义主题色的接口
interface ITheme {
    theme: string
}
// 初始化
export const initialTheme: ITheme = {
    theme: '等待改变主题'
}
// 建立一个Context实例
export const ThemeContext: Context<any> = createContext(initialTheme);

// (新增)初始化store的类型、初始化值、reducer
export const CHANGE_THEME: string = 'CHANGE_THEME';

// (新增)编写reducer函数
export const reducer = (state: ITheme, action: any) => {
    switch (action.type) {
        case CHANGE_THEME:
            return { ...state, theme: action.theme }
        default:
            throw new Error();
    }
}

/**
 * 建立一个 Theme 组件
 * Theme 组件包裹的全部子组件均可以经过调用 ThemeContext 访问到 value
 */
export const Theme: React.FC = (props) => {
    // 经过使用useReducer更新状态
    const [state, dispatch] = useReducer(reducer, initialTheme);
    return (
        <ThemeContext.Provider value={{state, dispatch}}>
            {props.children}
        </ThemeContext.Provider>
    )
}
复制代码

此时咱们的Theme组件已所有编写完毕。

Switch添加事件

咱们Switch也经过useContext来读取Theme的状态,而后在button内添加修改状态的dispatch,即可以修改Theme的状态了

修改后的Swtich组件

import React, { useContext } from 'react'
import { ThemeContext, CHANGE_THEME } from '../../store/theme'

const Switch: React.FC = () => {
    // 调用dispatch修改状态
    const { dispatch } = useContext(ThemeContext)

    return (
        <section>
            <button 
                onClick={() => {
                    dispatch({ type: CHANGE_THEME, theme: "Change One" });
                }}>
                我是改变状态1
            </button>
            <button
                onClick={() => {
                    dispatch({ type: CHANGE_THEME, theme: "Change Two" });
                }}>
                我是改变状态2
            </button>
        </section>
    )
}

export default Switch;
复制代码

运行项目效果以下

改变状态

至此,全部步骤完成。

demo已传到了GitHub,能够和你的代码进行对比😜

结尾

写这篇文章的初衷就是以为在React中使用一次Redux真的太麻烦了,有了React Hook后的确少了很多重复的操做,但愿能分享给你们。记得给我点个赞喔,算是对我一种鼓励吧,哈哈!

其余文章

如何根据背景颜色动态修改文字颜色

相关文章
相关标签/搜索