How to use the elegant react hooks to state management

In the process of using react and redux, there have been a problem, which needs to be placed redux state, the state needs to be saved in a local state within the assembly, in addition redux irrational use may bring chaotic state management issues, in addition for local state in terms of partial state, react hooks provided a class of setState better than one alternative. In this paper, starting from the state administration, talk about how to use hooks to the elegant state management.

  • How to use redux

  • react hooks管理local state

  • How to react hooks to solve the communication between components

Original in my blog: github.com/fortheallli... Subscribe


First, how to use redux

We must first clear why use redux, it is important, if you do not know why use redux, it certainly can not reasonably use redux in the process of developing the first look at the nature redux:

redux status as a management tool, mainly to solve the problem of communication between components.

Since it is a communication problem between components, then obviously the state of all pages are placed in redux, is unreasonable, complexity is very high.

(1) full amount redux

It also made this early in question, in the application, no matter what state, press the page-level routing split, all on redux, the page change any state, is achieved by react-redux of mapState and mapDispatch.

49c7cf7b08714e6b94e600e5d4c2e851


Redux status is updated from the state feedback to the view, the process is a long chain, an action from dispatch, and other logic to go reducer, a complete link comprising:

Create action, create redux middleware, create the appropriate type of reducer function that creates mapState and mapDispatch and so on.

If all states are stored in the redux, then each state must take this process a few steps, and cumbersome, there is no doubt increase the amount of code

Unreasonable mix (2) reduction of local state and the state redux

全量使用redux的复杂度很高,我们当然考虑将一部分状态放在redux中,一部分状态放在local state中,但是这种情况下,很容易产生一个问题,就是如果local State跟redux中的state存在状态依赖。

举例来说,在redux中的状态中有10个学生

 //redux
 
 students = [{name:"小白",score:70},{name:"小红",score:50}....]
复制代码

在local state中我们保存了分数在60分以上的学生

 // local state
 
 state = [{name:"小白",score:70}]
复制代码

如果redux中的学生改变了,我们需要从redux中动态的获取students信息,然后改变局部的state.结合react-redux,我们需要在容器组件中使用componentWillReceivedProps或者getDerivedStateFromProps这个声明周期,来根据props改变局部的local state.

componentWillReceivedProps这里不讨论,为了更高的安全性,在react中用静态的getDerivedStateFromProps代替了componentWillReceivedProps这里不讨论,而getDerivedStateFromProps这个声明周期函数在props和state变化的时候都会去执行,因此如果我们需要仅仅在props的改变而改变局部的local state,在这个声明周期中会存在着很复杂的判断逻辑。

redux中的状态和local state中的状态相关联的越多,getDerivedStateFromProps这个声明周期函数就越复杂

给我们的启示就是尽可能的减少getDerivedStateFromProps的使用,如果实在是redux和local state有关联性,用id会比直接用对象或者数组好,比如上述的例子,我们可以将学生分组,并给一个组号,每次在redux中的学生信息发生改变的时候会改变相应的组号。 这样在getDerivedStateFromProps只需要判断组号是否改变即可:

 class Container extends React.Component{
 state = {
 group_id:number
 }
 
 static getDerivedStateFromProps(props,state){ if(props.group_id!==state.group_id){
 
 ... 更新及格的学生
 }else{ return null
 }
 }
 }
复制代码

这里推荐https://github.com/paularmstrong/normalizr,如果实在redux和local state关联性强,可以先将数据范式化,范式化后的数据类似于给一个复杂结构一个id,这样子会简化getDerivedStateFromProps的逻辑.

(3)本节小结

如何使用redux,必须从redux的本质出发,redux的本质是为了解决组件间的通信问题,因此组件内部独有的状态不应该放在redux中,此外如果redux结合class类组件使用,可以将数据范式化,简化复杂的判断逻辑。

二、react hooks管理local state

前面将了应该如何使用redux,那么如何维护local state呢,React16.8中正式增加了hooks。通过hooks管理local state,简单易用可扩展。

在hooks中的局部状态常见的有3种,分别是useState、useRef和useReducer

(1) useState

useState是hooks中最常见的局部状态,比如:

 const [hide, setHide] = React.useState(false); const [name, setName] = React.useState('BI');
复制代码

理解useState必须明确,在react hooks中:

每一次渲染都有它自己的 Props and State

一个经典的例子就是:

 function Counter() { const [count, setCount] = useState(0); 
 function handleAlertClick() {
 setTimeout(() => {
 alert('You clicked on: ' + count);
 }, 3000);
 } 
 return ( <div>
 <p>You clicked {count} times</p>
 <button onClick={() => setCount(count + 1)}>
 Click me
 </button>
 <button onClick={handleAlertClick}>
 Show alert
 </button>
 </div>
 );
 }
复制代码

如果我按照下面的步骤去操作:

  • 点击增加counter到3

  • 点击一下 “Show alert”

  • 点击增加 counter到5并且在定时器回调触发前完成

猜猜看会alert出什么?

97cbe7508a1d4fc58b99cf540d0ac761


结果是弹出了3,alert会“捕获”我点击按钮时候的状态,也就是说每一次的渲染都会有独立的props和state.

(2) useRef

在react hooks中,我们知道了每一次的渲染都会有独立的props和state,那么如果我们需要跟类组件一样,每次都能拿到最新的渲染值时,应该怎么做呢?此时我们可以用useRef

useRef提供了一个Mutable可变的数据

我们来修改上述的例子,来是的alert为5:

 function Counter() { const [count, setCount] = useState(0) const late = useRef(0) function handleAlertClick() {
 setTimeout(() => {
 alert('You clicked on: ' + late.current)
 }, 3000)
 }
 useEffect(() => {
 late.current = count
 }) return ( <div>
 <p>You clicked {count} times</p>
 <button onClick={() => setCount(count + 1)}>Click me</button>
 <button onClick={handleAlertClick}>Show alert</button>
 </div>
 )
 }
复制代码

如此修改以后就不是alert3 而是弹出5

(3) useReducer

react hooks中也提供了useReducer来管理局部状态.

当你想更新一个状态,并且这个状态更新依赖于另一个状态的值时,你可能需要用useReducer去替换它们。

同样的用例子来说明:

 function Counter() {
 const [state, dispatch] = useReducer(reducer, initialState);
 const { count, step } = state;
 
 useEffect(() => {
 const id = setInterval(() => {
 dispatch({ type: 'tick' });
 }, 1000);
 return () => clearInterval(id);
 }, [dispatch]);
 
 return (
 <>
 <h1>{count}</h1>
 <input value={step} onChange={e => {
 dispatch({ type: 'step', step: Number(e.target.value)
 });
 }} />
 </>
 );
 }
 
 const initialState = { count: 0, step: 1,
 };
 
 function reducer(state, action) {
 const { count, step } = state; if (action.type === 'tick') {
 return { count: count + step, step };
 } else if (action.type === 'step') {
 return { count, step: action.step };
 } else { throw new Error();
 }
 }
复制代码

解释上面的结果主要来看useEffect部分:

 useEffect(() => { const id = setInterval(() => {
 dispatch({ type: 'tick' });
 }, 1000); return () => clearInterval(id);
 }, [dispatch]);
复制代码

在state中的count依赖与step,但是使用了useReducer后,我们不需要在useEffect的依赖变动数组中使用step,转而用dispatch来替代,这样的好处就是减少不必要的渲染行为.

此外:局部状态不推荐使用 useReducer ,会导致函数内部状态过于复杂,难以阅读。 useReducer 建议在多组件间通信时,结合 useContext 一起使用。

三、react hooks如何解决组件间的通信

react hooks中的局部状态管理相比于类组件而言更加简介,那么如果我们组件采用react hooks,那么如何解决组件间的通信问题。

###(1) UseContext

最基础的想法可能就是通过useContext来解决组件间的通信问题。

比如:

function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment }
}let Counter = createContext(null)function CounterDisplay() { let counter = useContext(Counter) return ( <div>
 <button onClick={counter.decrement}>-</button>
 <p>You clicked {counter.count} times</p>
 <button onClick={counter.increment}>+</button>
 </div>
 )
}function App() { let counter = useCounter() return ( <Counter.Provider value={counter}>
 <CounterDisplay />
 <CounterDisplay />
 </Counter.Provider>
 )
}
复制代码

在这个例子中通过createContext和useContext,可以在App的子组件CounterDisplay中使用context,从而实现一定意义上的组件通信。

此外,在useContext的基础上,为了其整体性,业界也有几个比较简单的封装:

github.com/jamiebuilds… github.com/diegohaz/co…

但是其本质都没有解决一个问题:

如果context太多,那么如何维护这些context

也就是说在大量组件通信的场景下,用context进行组件通信代码的可读性很差。这个类组件的场景一致,context不是一个新的东西,虽然用了useContext减少了context的使用复杂度。

###(2) Redux结合hooks来实现组件间的通信

hooks组件间的通信,同样可以使用redux来实现。也就是说:

在React hooks中,redux也有其存在的意义

在hooks中存在一个问题,因为不存在类似于react-redux中connect这个高阶组件,来传递mapState和mapDispatch, 解决的方式是通过redux-react-hook或者react-redux的7.1 hooks版本来使用。

  • redux-react-hook

在redux-react-hook中提供了StoreContext、useDispatch和useMappedState来操作redux中的store,比如定义mapState和mapDispatch的方式为:

import {StoreContext} from 'redux-react-hook';
ReactDOM.render( <StoreContext.Provider value={store}>
 <App />
 </StoreContext.Provider>,
 document.getElementById('root'),
);
import {useDispatch, useMappedState} from 'redux-react-hook';
export function DeleteButton({index}) {
 // Declare your memoized mapState function
 const mapState = useCallback( state => ({
 canDelete: state.todos[index].canDelete,
 name: state.todos[index].name,
 }),
 [index],
 );
 // Get data from and subscribe to the store
 const {canDelete, name} = useMappedState(mapState);
 // Create actions
 const dispatch = useDispatch();
 const deleteTodo = useCallback(
 () =>
 dispatch({
 type: 'delete todo',
 index,
 }),
 [index],
 );
 return ( <button disabled={!canDelete} onClick={deleteTodo}>
 Delete {name}
 </button>
 );
}
复制代码
  • react-redux 7.1的hooks版

这也是官方较为推荐的,react-redux 的hooks版本提供了useSelector()、useDispatch()、useStore()这3个主要方法,分别对应与mapState、mapDispatch以及直接拿到redux中store的实例.

简单介绍一下useSelector,在useSelector中除了能从store中拿到state以外,还支持深度比较的功能,如果相应的state前后没有改变,就不会去重新的计算.

举例来说,最基础的用法:

import React from 'react'import { useSelector } from 'react-redux'export const TodoListItem = props => { const todo = useSelector(state => state.todos[props.id]) return <div>{todo.text}</div>}
复制代码

实现缓存功能的用法:

import React from 'react'import { useSelector } from 'react-redux'import { createSelector } from 'reselect'const selectNumOfDoneTodos = createSelector( state => state.todos,
 todos => todos.filter(todo => todo.isDone).length
)export const DoneTodosCounter = () => { const NumOfDoneTodos = useSelector(selectNumOfDoneTodos) return <div>{NumOfDoneTodos}</div>}export const App = () => { return ( <>
 <span>Number of done todos:</span>
 <DoneTodosCounter />
 </>
 )
}
复制代码

在上述的缓存用法中,只要todos.filter(todo => todo.isDone).length不改变,就不会去重新计算.

四、总结

react in the complete status management into local and global state state, and react hooks simplified partial state, so that the management and control of the local partial states render extremely convenient, but still essentially react hooks on a view of the component layers, and there is no perfect solve communication problems between the components, that is to say, it does not contradict the redux and other state management unit and react hooks nature.

In my practice, with redux realize the communication between the components and react hooks to achieve local state management, so that the code is simple read, but also reduce a lot of unnecessary redux boilerplate code.

43880ccb6f324006aaf443799cdec753


Guess you like

Origin blog.51cto.com/14516511/2439688