React Hooks下的全局狀態管理

React Hooks下的全局狀態管理

React Hooks

React Hooks 是 React 16.7.0-alpha 版本推出的新特性,旨在解決組件間共享狀態邏輯的問題。javascript

  • **useState: **容許在 function 組件中,聲明和改變狀態。在此以前,只有 class 組件能夠。(useImmer
  • **useEffect: **容許在 function 組件中,抽象地使用 React 的生命週期函數。開發者可使用更函數式的、更清晰的 hooks 的方式。

使用 hooks 對帶有本地狀態的 Avatar 組件進行重構說明:html

import React, { useState, useEffect } from 'react';
const Avatar = () => {
	// 建立 user 狀態和修改狀態的函數
	const [user, setUser] = useState("馬飛");
	// 默認 componentDidMount/componentDidUpdate 時會觸發回調
	// 也可使用第二個參數,指定觸發時機
	useEffect(() => {
		document.title = 當前用戶:${user};
	});
	// 使用 setUser 改變狀態
	return <div onClick={() => setUser("mafeifei")}>{user}; }; 複製代碼
  • useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
複製代碼

useState 的替代方案。它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法,提供了組件內使用redux的方式管理狀態。
如下是用 reducer 重寫 useState 一節的計數器示例:java

const initialState = {count: 0};
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}
function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } 複製代碼

hooks官方文檔react

Redux

做爲目前最流行的狀態管理框架,Redux在7.1.0版本中也提供了對於Hooks的支持。 git

API

store和reducer部分和原先保持一致,外層也須要包裝Providergithub

const store = createStore(rootReducer)

ReactDOM.render(
  <Provider store={store}> <App /> </Provider>,
  document.getElementById('root')
)
複製代碼

在獲取和更新store數據上,Redux針對Hooks提供了新的API。 redux

useSelector()

const result : any = useSelector(selector : Function, equalityFn? : Function)
// useSelector(state => state.count)
複製代碼

selector概念上和connect的mapStateToProps參數類似,將store中的state做爲第一個參數,在每次component render時都會調用。在數據的更新上,useSelector會訂閱store,當有action執行時會從新調用selector獲取最新的數據。和mapStateToProps不一樣的是useSelector默認使用===比較更新先後的數據,因此提供了第二個參數equalityFn來定製比較函數。 api

useDispatch

const dispatch = useDispatch()
複製代碼

useDiapatch返回dispatch的引用,用來調用action。
框架

useStore()

const store = useStore()
複製代碼

useStore會返回Redux中store的引用,可使用store.getState()獲取store中的state。
dom

Demo

reducer可配合immer使用
codesandbox

import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider, useSelector, useDispatch } from "react-redux";
import {produce} from 'immer'

const initState = {
  count: 0
};

const reducer = (state = initState, action) => produce(state, draft => {
  switch (action.type) {
    case "increase": {
      draft.count += 1
      break
    }
    case "decrease": {
      draft.count += 1
      break
    }
    default:
  }
})

const store = createStore(reducer);

const Counter = () => {
  const count = useSelector(state => state.count); // useSelector
  const dispatch = useDispatch(); // useDispatch
  return (
    <div> <h1>Counter: {count}</h1> <button onClick={() => dispatch({ type: "increase" })}>Increase</button> <button onClick={() => dispatch({ type: "decrease" })}>Decrease</button> </div>
  )
}
const App = () => {
  return (
    <Provider store={store}> <Counter /> </Provider>

  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App />, rootElement ); 複製代碼

New Context API + Hooks

useContext

React Hooks提供了useContext來訪問context,能夠在使用 Consumer 的時候沒必要包裹 Children

import React, { useContext } from 'react';

function Display() {
  const value = useContext(NumberContext);
  return <div>The answer is {value}.</div>;
}
複製代碼

useReducer

useReducer是hooks提供的針對複雜場景替代useState管理狀態的API,提供了action和reducer的方式來管理組件內狀態,它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法。

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } 複製代碼


文章(link)提供了一種context配合hooks實現全局狀態管理的一種思路,相較於純context,配合hooks的useReducer提供了更爲強大的狀態管理能力。

import React, {createContext, useContext, useReducer} from 'react';
export const StateContext = createContext();

export const StateProvider = ({reducer, initialState, children}) =>(
  <StateContext.Provider value={useReducer(reducer, initialState)}> {children} </StateContext.Provider> ); export const useStateValue = () => useContext(StateContext); 複製代碼


首先新建一個StateContext,而後再StateContext的Provider中將context的值設置爲useReducer(reducer, initialState),這樣當調用action執行reducer後,context的值也將發生變化,似的引用context值得組件更新。由useReducer也保證了只有在數據變化是纔會更新組件。
在訪問context數據方便也作了一層useStateValue hooks封裝,不須要在每一個組件裏調用useContext。

demo

codesandbox

import React, { createContext, useContext, useReducer } from "react";
import { render } from "react-dom";
import { produce } from "immer";

const StateContext = createContext();

const StateProvider = ({ reducer, initialState, children }) => (
  <StateContext.Provider value={useReducer(reducer, initialState)}> {children} </StateContext.Provider> ); const useStateValue = () => useContext(StateContext); function App() { const initialState = { count: 0 }; const reducer = (state = initialState, action) => produce(state, draft => { switch (action.type) { case "increase": { draft.count += 1; break; } case "decrease": { draft.count += 1; break; } default: } }); return ( <StateProvider initialState={initialState} reducer={reducer}> <Count /> </StateProvider> ); } const Count = () => { const [{ count }, dispatch] = useStateValue(); return ( <div> <h1>Counter: {count}</h1> <button onClick={() => dispatch({ type: "increase" })}>Increase</button> <button onClick={() => dispatch({ type: "decrease" })}>Decrease</button> </div> ); }; const rootElement = document.getElementById("root"); render(<App />, rootElement); 複製代碼

unstated-next

unstated是react社區中比較流行的輕量級的狀態管理工具,在api設計的時候沿用react的設計思想,可以快速的理解和上手。
Unstated拋出三個對象,分別是Container、Subscribe和Provider。
Unstated會使用React.createContext來建立一個StateContext對象,用來進行狀態的傳遞。

unstated demo

import React from 'react';
import { render } from 'react-dom';
import { Provider, Subscribe, Container } from 'unstated';

type CounterState = {
  count: number
};

class CounterContainer extends Container<CounterState> {
  state = {
    count: 0
  };

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  decrement() {
    this.setState({ count: this.state.count - 1 });
  }
}

function Counter() {
  return (
    <Subscribe to={[CounterContainer]}> {counter => ( <div> <button onClick={() => counter.decrement()}>-</button> <span>{counter.state.count}</span> <button onClick={() => counter.increment()}>+</button> </div> )} </Subscribe>
  );
}

render(
  <Provider> <Counter /> </Provider>,
  document.getElementById('root')
);
複製代碼


隨着Hooks的推出,Unstated也推出了符合hooks api的新一代狀態管理庫unstated-next,在API設計上延續了Unstated貼近react操做的風格,使用自定義hooks來管理狀態。

createContainer(useHook)

建立container,接受一個封裝狀態的自定義hook參數

import { createContainer } from "unstated-next"
function useCustomHook() {
  let [value, setValue] = useState()
  let onChange = e => setValue(e.currentTarget.value)
  return { value, onChange }
}
let Container = createContainer(useCustomHook)
複製代碼

<Container.Provider initialState>

保留了unstated中的Provider

function ParentComponent() {
  return (
    <Container.Provider initialState={"value"}> <ChildComponent /> </Container.Provider> ) } 複製代碼

useContainer(Container)

在組件內經過useContainer獲取自定義hook中的狀態和操做

import { useContainer } from "unstated-next"
function ChildComponent() {
  let input = useContainer(Container)
  return <input value={input.value} onChange={input.onChange} /> } 複製代碼

Demo

codesandbox

import { render } from "react-dom";
import React, { useState } from "react";
import { createContainer } from "unstated-next";

function useCounter(initialState = 0) {
  let [count, setCount] = useState(initialState);
  let increase = () => setCount(count + 1);
  let decrease = () => setCount(count - 1);
  return { count, increase, decrease };
}
let Counter = createContainer(useCounter);

function App() {
  let counter = Counter.useContainer();
  return (
    <div> <h1>Counter: {counter.count}</h1> <button onClick={counter.increase}>Increase</button> <button onClick={counter.decrease}>Decrease</button> </div>
  );
}

render(
  <Counter.Provider> <App /> </Counter.Provider>, document.getElementById("root") ); 複製代碼

mobx-react-lite

做爲流行度僅此於Redux的狀態管理組件也針對hooks進行了匹配,推出了新一代的狀態管理庫mobx-react-lite,再也不使用inject將store注入component,而是配合react的新context api將store放入context管理。

demo

codesandbox

import { observable } from 'mobx'
import { Observer, useObserver, observer } from 'mobx-react' // 6.x or mobx-react-lite@1.4.0
import ReactDOM from 'react-dom'

const person = observable({
  name: 'John',
})

// named function is optional (for debugging purposes)
const P1 = observer(function P1({ person }) {
  return <h1>{person.name}</h1>
})

const P2 = ({ person }) => <Observer>{() => <h1>{person.name}</h1>}</Observer>

const P3 = ({ person }) => {
  return useObserver(() => <h1>{person.name}</h1>)
}

ReactDOM.render(
  <div>
    <P1 person={person} />
    <P2 person={person} />
    <P3 person={person} />
  </div>,
)

setTimeout(() => {
  person.name = 'Jane'
}, 1000)
複製代碼

比較

  • Redux是社區最流行的狀態管理工具,生態較好,支持middleware,提供了可預測的狀態管理,但須要寫大量的模板代碼,且包體積在狀態管理工具中相對較大,適合大型項目
  • context + hooks提供了基本的狀態管理功能,配合hooks中的useReducer能夠支持大部分redux提供的功能,且不須要引入其餘庫,適合全局狀態較簡單的項目
  • unstated將狀態和操做封裝在container中,優勢是在風格上比redux更貼近react的操做方式,也更容易上手,僅有200b,可是相比context+useReducer的方式沒什麼優點,除非原來就使用了unstated的項目,不然並不推薦。
  • mobx-react-lite延續了mobx的優勢,將狀態維護在observable的對象中管理,採用代理的方式,支持直接操做對象,相比redux在代碼寫法上較爲簡潔,但對於大型項目,mobx較爲靈活的方式不如reducer action->reducer->state的流程更清晰,比較適合中小型項目。
相關文章
相關標籤/搜索