Skip to main content

Redux, Context & useReducer

 React State Management Tool: Redux


Concept: 状态提升 (Lifting State Up)

Definition: several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.


Example:

In React website, it gives an example. Fahrenheit & Centigrade

This data could be put in their shared parent component.

i.e., (if using class component)

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});  }

  render() {
    const scale = this.state.scale;    const temperature = this.state.temperature;    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}          onTemperatureChange={this.handleCelsiusChange} />        <TemperatureInput
          scale="f"
          temperature={fahrenheit}          onTemperatureChange={this.handleFahrenheitChange} />        <BoilingVerdict
          celsius={parseFloat(celsius)} />      </div>
    );
  }
}



Built-in Hooks

1) Context

- Can skip layers to pass data

- Similar to provide/inject in Vue

- Application: Change theme, change language


For example, we want to pass ThemeContext.

In parent component:

const themes = {
light: {
fore: "#000",

background: "#eee",
},

dark: {
xxx,
},
};

export const ThemeContext = createContext(themes.light);

const [theme, setTheme] = useState(themes.light);

<ThemeContext.Provider value={theme}>
<xxComponent />
</ThemeContext.Provider>;


In child component:

const theme = useContext(ThemeContext);
--------------------------------------------
const style = { color: theme.fore, background: theme.background };


then, we can directly use style



2) useReducer

Alternative solution of useState()

example:

type StateType = { count: number };
type ActionType = { type: string };

const initialState: StateType = { count: 100 };

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

const TryReducer: FC = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<span>Count: {state.count}</span>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
};


However, when using reducer, parallel component might not be updated.

In this way, we can use Context in parent component to share reducer for its children components


In parent component write:


export const TodoContext = createContext({

state: initialState,

dispatch: (action: ActionType) => {}

})



const [state, dispatch] = useReducer( reducer, initialState);



<TodoContext.Provider value = ({ state, dispatch})>
</TodoContext.Provider>


In child component write:

const { state, dispatch } = useContext(TodoContext);

Then, can use all the data in child component


Redux

Why using Redux?

When the structure is complexed, it is hard to manage all the data and pass them using props

Therefore, it is necessary to use Redux.


React-Redux & Redux Toolkit

Install:

npm install @reduxjs/toolkit react-redux


Step:

1. create store folder under src

2. create index.ts

e.g.,

import { configureStore } from "@reduxjs/toolkit";
import countReducer from "./count";

export type StateType = {
count: number;
};

export default configureStore({
reducer: {
count: countReducer,
},
});

3. create count.ts (an example file, module name)

import { createSlice } from "@reduxjs/toolkit";

const INIT_STATE = 100;

export const countSlice = createSlice({
name: "count", // name of the module (can separate different modules)
initialState: INIT_STATE,
reducers: {
increase(state: number) {
return state + 1;
},
decrease(state: number) {
return state - 1;
},
},
});

export const { increase, decrease } = countSlice.actions;

export default countSlice.reducer;

here, createSlice already uses immer under the hood, which allows us to operate on the state, for example:

// Change selected ID
changeSelectedId: (
state: ComponentStateType,
action: PayloadAction<string>
) => {
state.selectedId = action.payload;
},


4. Use Provider and store in index.tsx

import { Provider } from "react-redux";
import store from "./store/index";
--------------------------------------
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>

5. In the page we want to use data, e.g., Count.tsx

import React, { FC } from "react";
import { useSelector, useDispatch } from "react-redux";
import { increase, decrease } from "./store/count";
import { StateType } from "./store/index";

const Count: FC = () => {
const count = useSelector<StateType>((state) => state.count);

const dispatch = useDispatch();
return (
<div>
<span>
<>Count: {count}</>
</span>
<button onClick={() => dispatch(increase())}>+</button> // be careful: dispatch()
<button onClick={() => dispatch(decrease())}>-</button> // increase() & decrease()
</div>
);
};

export default Count;


Redux 单向数据流 (one way data flow)

Store

当前 Redux 应用的状态存在于一个名为 store 的对象中。

store 是通过传入一个 reducer 来创建的,并且有一个名为 getState 的方法,它返回当前状态值:

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}


Dispatch

Redux store 有一个方法叫 dispatch更新 state 的唯一方法是调用 store.dispatch() 并传入一个 action 对象。 store 将执行所有 reducer 函数并计算出更新后的 state,调用 getState() 可以获取新 state。

store.dispatch({ type: 'counter/incremented' })

console.log(store.getState())
// {value: 1}

dispatch 一个 action 可以形象的理解为 "触发一个事件"。发生了一些事情,我们希望 store 知道这件事。 Reducer 就像事件监听器一样,当它们收到关注的 action 后,它就会更新 state 作为响应。


Selectors

Selector 函数可以从 store 状态树中提取指定的片段。随着应用变得越来越大,会遇到应用程序的不同部分需要读取相同的数据,selector 可以避免重复这样的读取逻辑:

const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2


State 是只读的

更改状态的唯一方法是 dispatch 一个 action,这是一个描述所发生事件的对象。

这样,UI 就不会意外覆盖数据,并且更容易跟踪发生状态更新的原因。由于 actions 是普通的 JS 对象,因此可以记录、序列化、存储这些操作,并在以后重放这些操作以进行调试或测试。


使用 Reducer 纯函数进行更改

若要指定如何基于 action 更新状态树,请编写 reducer 函数。Reducers 是纯函数,它接收旧 state 和 action,并返回新 state。与任何其他函数一样,你可以将 Reducer 拆分为较小的函数以帮助完成工作,或者为常见任务编写可重用的 Reducer。


  • Redux 的意图可以总结为三个原则

    • 全局应用状态保存在单个 store 中
    • store 中的 state 是只读的
    • Reducer 函数用于更新状态以响应 actions
  • Redux 使用“单向数据流”
    • State 描述了应用程序在某个时间点的状态,UI 基于该状态渲染


    • 当应用程序中发生某些事情时:

      • UI dispatch 一个 action
      • store 调用 reducer,随后根据发生的事情来更新 state
      • store 通知 UI state 发生了变化
    • UI 基于新 state 重新渲染



MobX: 声明式修改数据 

State 数据

action 动作

derivation 派生: computed observer







Comments

Popular posts from this blog

Templates

  Template - Polymorphism is the ability of the same code to operate on different types. This ability to operate on multiple types reduces code duplication by allowing the same piece of code to be reused across the different types it can operate on. - Polymorphism comes in a variety of forms. What we are interested in at the moment is parametric polymorphism, meaning that we can write our code so that it is parameterized over what type it operates on.  -That is, we want to declare a type parameter T and replace int with T in the above code. -Then, when we want to call the function, we can specify the type for T and get the function we desire. C++ provides parametric polymorphism through templates. Templated Functions - We can write a templated function by using the keyword template followed by the template parameters in angle brackets (<>). - Unlike function parameters, template parameters may be types, which are specified with typename where the type of the parameter wo...

useMemo的使用场景

 useMemo是用来缓存 计算属性 的。 计算属性是函数的返回值,或者说那些以返回一个值为目标的函数。 有些函数会需要我们手动去点击,有些函数是直接在渲染的时候就执行,在DOM区域被当作属性值一样去使用。后者被称为计算属性。 e.g. const Component = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 = () => { console . log ( "我需要手动调用,你不点击我不执行" ); setParams1 (( val ) => val + 1 ); }; //这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。 const computedFun2 = () => { console . log ( "我又执行计算了" ); return params2 ; }; return ( < div onClick = { handleFun1 } > //每次重新渲染的时候我就会执行 computed: { computedFun2 () } </ div > ); }; 上面的代码中,在每次点击div的时候,因为setParams1的缘故,导致params1改变,整个组件都需要重新渲染,也导致comptedFunc2()也需要重新计算。如果computedFunc2的计算量很大,这时候重新计算会比较浪费。 可以使用useMemo: const Com = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 ...

Valgrind

  Using Valgrind to check memory How to use Valgrind -To valgrind the program, run the valgrind command and give it our program name as an argument. -For example, if we want to run ./myProgram hello 42, we can simply run Valgrind ./myProgram hello 42.  Uninitialized Values -When we run the program, we may use uninitialized values. It needs to be fixed. Valgrind can tell us about the use of uninitialized values. But it only tell when the control flow of the program depends on the unitialized value. For example, uninitialized value appears in the conditional expression of an if, or a loop, or in the switch statement. -If we want to know where the uninitialized value is from, we can use Valgrind    --track-origins=yes ./myProgram -Using -fsanitize=address can find a lot of problems that Memcheck cannot.  -We can use Valgrind with GDB to debug. We can run: --vgdb=full --vgdb-error=0., then Valgrind will stop on the first error that it encounters and give control to ...