【大白话】说 redux & react-redux

145 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情

由Vue转React,耕耘下redux笔记

redux中文官方文档

react-redux API文档

建议写个todoList来练手,以此熟悉该技术: 刚写的todolist ,可clone玩玩。


前言:React-Redux, Redux, React三者之间的关系

一张图说清 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组件触发逻辑。

下面开始分别介绍:

Contants:

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

比如删除逻辑的字段:

export const TODO_DEL = "TODO_DEL";

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

在 Redux 中,action 本质是一个JavaScript 普通对象,可以理解为 store 数据的载体。

官网说:唯一改变 state 的方法就是触发 action,那是因为在UI组件中执行了dispatch(todoDel (!nowDone)); 操作, dispatch 了 action 暴露出来的 todoDel ,然后这个 todoDel 执行了 reduce 逻辑,然后才发生了 state 改变。有点绕,现在看不懂没关系,先在这打个标记,往下看,一会往回翻就明白了。

//action文件
export const todoDel = (id) => ({
  type: TODO_DEL,
  id,
});

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

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 数据。

//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组件触发事件,更新数据了。

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

// 打印初始状态
console.log(store.getState())

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

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

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

数据流

Redux的数据流在上面在开始的地方已经都是说过了,这里无非就是在重诉一遍,啰嗦一下。Redux 应用中数据的生命周期遵循下面 4 个步骤:

  1. 调用Action,可以在任何地方 store.dispatch(action)
//Action 就是一个描述“发生了什么”的普通对象 
{ type: 'LIKE_ARTICLE', articleId: 42 }
  1. store将(当前的 state 树和 action) 传入 reducer 函数():
// 当前应用的 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 树的一个分支。

//假如有两个 reducer 
 function A(state = [], action) {
   return nextState
 }

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

 let todoApp = combineReducers({
   A,
   B
 })

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

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

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

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

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

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

一些Api

说完redux,那就离不开这里面的一些Api和一些react的一些Hooks了,都很重要。

useSelector

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

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

createStore

通过createStore将state存入store

const store = createStore(reducer, initialState);

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

 <Provider store={store}>
     <Son />
 </Provider>

useDispatch

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

import { useDispatch } from "react-redux";
const dispatch = useDispatch();
dispatch(todoDel(id));

context :createContext -> useContext

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

//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 差不多

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

//父组件
function Father(){
   const [age,setAge]=useState(0)
   const handleClick = () => {
       setAge(age + 1)
   }
   return <Son />
}

采用useCallback +Memo后:

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

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

以后在更吧,现在写倦了...