你仍是在用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後的確少了很多重複的操做,但願能分享給你們。記得給我點個贊喔,算是對我一種鼓勵吧,哈哈!

其餘文章

如何根據背景顏色動態修改文字顏色

相關文章
相關標籤/搜索