由 Vue 转 React,耕耘下 redux 笔记
redux 中文官方文档 > react-redux API 文档

建议写个 todoList 来熟悉新技术:可 clone 刚写的 todolist

在这里插入图片描述

一张图说清 React-Redux ,Redux , React 三者到底是什么关系。

  • Redux: 首先 Redux 是一个应用状态管理 js 库,它本身和 React 是没有关系的,换句话说,Redux 可以应用于其他框架构建的前端应用,甚至也可以应用于 Vue 中。
  • React-Redux:React-Redux 是连接 React 应用和 Redux 状态管理的桥梁。React-redux 主要专注两件事,一是如何向 React 应用中注入 redux 中的 Store ,二是如何根据 Store 的改变,把消息派发给应用中需要状态的每一个组件。
  • React :就不说了
    在这里插入图片描述

redux 篇

以 todolist 为例,redux 在使用中大致表现为 UI 组件、action、constants、reduces 四大部分:
在这里插入图片描述
下面开始分别介绍,大致流程为:首先在 constants 设置 type 参数,接着 action 声明要修改的对象类型(补上 type 参数),然后在 reduce(接收旧的 state 和 action 两个参数,返回新的 state)写修改数据的逻辑,最后在 UI 组件触发逻辑。

Contents:设置 Action 需要的 type 参数字段,Reduce 会依据这字段做不同逻辑

1
export const TODO_DEL = 'TODO_DEL'

Action:只描述 state 的变化而不更新

在 Redux 中,action 本质是一个 JavaScript 普通对象,可以理解为 store 数据的载体。
官网说:唯一改变 state 的方法就是触发 action,那是因为在 UI 组件中,dispatch(todoDel (!nowDone)); dispatch 了 action 暴露出来的 todoDel ,然后这个 todoDel 执行了 reduce 逻辑,然后发生了 state 改变。

1
2
3
4
export const todoDel = (id) => ({
type: TODO_DEL,
id,
})

在 UI 组件里,只需把 action 创建的结果传给 dispatch() 方法,即可发起一次修改或更新数据(前提是 reduce 里写了逻辑)。

1
2
3
4
5
dispatch(todoDel(text))
//或者创建一个被绑定的 action 创建函数 来自动 dispatch:
const boundAddTodo = (text) => dispatch(addTodo(text))
//然后直接调用它们:
boundAddTodo(text)

store 里能直接通过 store.dispatch() 调用 dispatch() 方法,但多数情况下会使用 react-redux 提供的 connect() 来调用。bindActionCreators() 可以自动把多个 action 创建函数绑定到 dispatch() 方法上。


Reducer:根据 Action 变化更新 State

Reducers :就是纯函数,它接收旧的 state 和 action 两个参数,返回新的 state 的函数。且依据应用状态的变化响应 actions 并将数据更新。

注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。

1
2
3
4
5
6
7
8
9
10
11
12
//TODO_DEL:依据type参数,处理不同的action逻辑
export default function todo(state = 旧state数据, action) {
switch (action.type) {
case TODO_DEL:
return state.filter((item) => item.id !== action.id);
case TODO_CHEK_ALL:
return state.map((item) => ({ ...item, done: action.done }));
...
default:
return state;
}
}

永远不要在 reducer 里做这些操作:

  • 修改传入参数;
  • 执行有副作用的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now()Math.random()
  • 使用 combineReducers() 将多个 reducer 合并成为一个

谨记 reducer 一定要保持纯净。只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

UI 组件

上面一套流程走完后,就可以在 UI 组件触发事件,更新数据了。

1
2
3
4
5
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const handleChangeAll = () => {
dispatch(todoCheck(!nowDone))
}

Store:将它们联系到一起的对象

创建一个 Redux store 来以存放应用中所有的 state:

createStore(reducer, [preloadedState], enhancer)

  • reducer:接收旧的 state 和 action,返回新的 state 树

Store 有以下职责:

发起 Actions

1
2
3
4
5
6
7
8
9
10
11
// 打印初始状态
console.log(store.getState())

// 注意 subscribe() 返回一个函数用来注销监听器
const unsubscribe = store.subscribe(() => console.log(store.getState()))

// 发起一系列 action
store.dispatch(reducer的方法('Learn about actions'))

// 停止监听 state 更新
unsubscribe()

数据流

Redux 应用中数据的生命周期遵循下面 4 个步骤:

  1. 调用 Action,可以在任何地方 store.dispatch(action)
1
2
//Action 就是一个描述“发生了什么”的普通对象
{ type: 'LIKE_ARTICLE', articleId: 42 }
  1. store 将(当前的 state 树和 action) 传入 reducer 函数():
1
2
3
4
5
6
7
8
9
10
11
12
13
// 当前应用的 state
let previousState = {
A: [],
B: 'hello',
}

// 将要执行的 action
let action = {
type: 'ADD_TODO',
text: 'Understand the flow.',
}

let nextState = todoApp(previousState, action)
  1. 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树

Redux 原生提供combineReducers()辅助函数,来把根 reducer 拆分成多个函数,用于分别处理 state 树的一个分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
//假如有两个 reducer
function A(state = [], action) {
return nextState
}

function B(state = 'Redux', action) {
return nextState
}

let todoApp = combineReducers({
A,
B,
})

当你触发 action 后,combineReducers 返回的 todoApp 会负责调用两个 reducer:

1
2
let nextA = A(state.A, action)
let nextB = B(state.B, action)

然后会把两个结果集合并成一个 state 树:

1
2
3
4
return {
A: nextA,
B: nextB,
}
  1. Redux store 保存了根 reducer 返回的完整 state 树

这个新的树就是应用的下一个 state!所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。

React Redux 应该调用 component.setState(newState) 来更新。

一些 Api

useSelector

selector 回调函数会把 storeState 返回给你 你再进行筛选返回自己想要使用的数据

1
const num = useSelector((state) => state.num)

createStore

通过 createStore 将 state 存入 store

1
const store = createStore(reducer, initialState)

再通过 Provider 向子组件暴露 store,通过 store 在父子组件之间共享状态

1
2
3
<Provider store={store}>
<Son />
</Provider>

useDispatch

通过useDispatch 可以获取 dispatch,用来提交更新

1
2
3
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
dispatch(todoDel(id))

context :createContext -> useContext

一种组件传值方式,能轻松拿到组件值。官方 Hooks 的 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//null可以为父组件要传递的值,不一定是null
const newContext = createContext(null);

const [age,setAge] = setAge(0)
//通过value传递值
<newContext.Provider value={{age,setAge}}>
<Father>
<Son />
</Father>
</newContext.Provider >

//son组件
const {age,setAge} = useContext(newContext)
const add=()=>{
setAge(age=>age+1)
};

useEffect(callback,[])

React 会在每次渲染完后调用 useEffect,包括第一次加载渲染 DOM

接受两个参数,一个处理函数,另一个关联的状态或数组,这个变了就重新执行

useCallback + useMemo

他两个使用和 useEffect 差不多

解决的痛点:在函数组件中,定义在组件内的函数会随着状态更新而重新渲染,这样会影响的子组件频繁定义、渲染。

1
2
3
4
5
6
7
8
//父组件
function Father() {
const [age, setAge] = useState(0)
const handleClick = () => {
setAge(age + 1)
}
return <Son />
}

采用 useCallback +Memo 后:

1
2
3
4
5
6
7
8
//使用Memo后确实不会影响了,但父组件传值过来呢
const Son = React.memo(function Son(){
...
})

//父组件:这样就不会
const childClick = useCallback(() => {}.[])
<Son click={childClick} />