React学习笔记(九)--- Redux相关

一、简介

​ 什么是Redux?官网文档是这么说的:Redux 是一个使用叫做“action”的事件来管理和更新应用状态的模式和工具库 ,它以集中式Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。简单来说,Redux就是一个管理全局状态的JS库,把一些在项目的很多页面中都使用到的状态,集中进行管理,并且能很方便的在整个项目的任何页面使用,类似于Vue的Vuex。

React学习笔记(九)--- Redux相关_第1张图片

​ Redux可以应用于React、Vue、Angular等框架,但基本上只在React中与React-Redux结合使用。

​ 什么情况下使用Redux?总体原则: 能不用就不用, 如果不用比较吃力才考虑使用,Redux 并非适用所有项目,只有当项目出现以下情况时,你才可以考虑使用它:① 在项目的很多组件中,都重复使用了某组件的某个状态。 ② 重复使用的状态会频繁更新,更新状态的逻辑很复杂。③ 一个组件要改变另一个组件的状态。④ 某个状态需要在任何地方都能拿到。

​ Redux Toolkit 是我们推荐的编写 Redux 逻辑的方法。 它包含我们认为对于构建 Redux 应用程序必不可少的包和函数,并简化了大多数 Redux 任务,使编写 Redux 应用程序变得更加容易。

二、Redux重要概念

1、action(核心)

​ action 是Redux 用来描述应用发生什么事件,是一个具有 type 字段的JS对象。 type 字段是字符串类型的,相当于描述这个action的名字,通常形式为 域/事件名称,第一部分是这个 action 所属的特征或类别,第二部分是发生的具体事情。

​ action 还可以有一个payload字段,用来包含有关发生事件的附加信息。

// 一个典型的 action对象
const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}
action creator:

​ 创建并返回一个 action 对象的函数,让我们不需要每次都手动编写action。

const addTodo = text => {
  return {
    type: 'todos/todoAdded',
    payload: text
  }
}
action type:

​ action的type字段在reducer和action creator很多地方都会用到,当项目过大,内容很多,就会导致type的字符串容易写错,而且一一修改也很麻烦。所以我们可以统一处理常用的type字段,比如在 store 目录中创建 actionTypes 目录文件或 constants 目录文件,集中处理使用常量来存储 action type。用到存储的type时,只需要引入对应文件即可,这样修改也只需要修改该文件就能影响整个项目。

export const SET_NAME = 'user/setName'
export const SUB_MORE = 'money/subMore' 

2、Reducer(核心)

​ reducer 是一个纯函数,接收当前的 state 和一个 action 对象,决定如何更新状态,并返回更新后的状态。类似于一个事件监听器,根据接收到的action(事件)来决定处理事件的逻辑,如果reducer关心这个action,则复制 state,并使用新值更新 state 副本,然后返回新 state。如果不关心,则返回原来的state不变。

​ Reducer的使用规则:① 仅使用state和action参数计算最新的状态值。② 禁止直接修改state,必须通过复制当前state,然后对复制的值进行更新的方式来做 不可变更新。③ 禁止任何一步逻辑、依赖随机值或导致其他副作用的代码。

// 当前state
const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // 检查 reducer 是否关心这个 action
  if (action.type === 'counter/increment') {
    // 如果关心,则复制 `state`
    return {
      ...state,
      // 使用新值更新 state 副本
      value: state.value + 1
    }
  }
  // 如果不关心,则返回原来的 state 不变
  return state
}

3、Store(核心)

​ store 对象是存储当前Redux应用 state 状态的仓库,可以把它看成一个数据源,对所有的数据进行统一管理,一个应用只能有一个store。store 需要通过createStore 或 Redux Toolkit 的configureStore(推荐)来创建,创建时需要传入一个reducer 作为参数,并具有一个名为 getState() 的方法来获取当前状态值。

// 导入创建方法
import { configureStore } from '@reduxjs/toolkit'
// 创建store
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
/*------------ 或者 -------------*/
// 导入创建方法
import { createStore } from 'redux'
// 创建store
const store = createStore(counterReducer)
console.log(store.getState())

4、Dispatch

​ store 还有一个方法 叫做 dispatch(),更新 store 中的 state 的唯一方法是调用 store.dispatch(action) 方法,参数为一个action对象。调用该方法之后,store 将执行所有 reducer 函数并计算出更新后的 state,然后调用 getState() 可以获取最新 state。

​ dispatch 一个 action 可以理解为触发了一个”事件“,而Reducer就像一个事件监听器,当收到了关注的 action 之后,就会更新state。

// dispatch 一个 action  
store.dispatch({ type: 'counter/increment' })
// 获取最新的state
console.log(store.getState())
// {value: 1}

5、Selector

​ Selector 函数可以从 store 状态库中提取指定的 state ,因为 getState() 获取的是全部的 state ,想要获取其中部分 state ,就需要从全部的 state 进行读取操作,如果我们在项目的很多地方都用到了相同的那部分state,此时就可以通过设置 Selector 函数来避免重复的读取逻辑。

// 将获取部分状态的逻辑封装到 Selector 函数中
const selectCounterValue = state => state.value
// 调用 Selector 函数 并将全部state作为参数传入
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)  // 2

6、单向数据流(one-way data flow)

​ 首先我们需要了解三个概念:

state:驱动数据的真实数据状态。
view:基于当前状态渲染的UI视图。
actions:根据用户的行为,触发状态的更新。

​ 了解以上概念之后,所谓的单向数据流就是:应用基于当前的state渲染出View,当用户对应用做出某种行为(如点击事件等),state就会根据行为进行更新,生成新的state,然后应用会基于新的state 重新渲染View。

​ 对于 Redux 我们可以将单向数据流进一步细化:

初始启动时:

​ ① 使用最顶层的 root reducer 函数创建 Redux store。

​ ② store 调用一次 root reducer,并将返回值保存为初始值。

​ ③ UI首次渲染时,UI组件访问 store 的当前 state,获取数据渲染页面。同时订阅监听 store 的更新。

更新环节:

​ ① 用户做出某种行为(如点击事件),就会 dispatch 一个 action 到 store。

​ ② action 触发关心它type的 reducer,reducer根据当前state和 action,更新 state 副本,然后返回新 state。

​ ③ store会通知所有订阅 store 的UI组件,通知他们store发生更新。

​ ④ 接收到通知的UI组件会去检查自己依赖的 state 部分是否发生更新,如果更新,则相应的UI组件也会使用新 state 来渲染视图。没更新则不会重新渲染。

7、可变性与不可变性

​ “Mutable” 意为 “可改变的”,而 "immutable 意为永不可改变。可改变的是指在内存引用地址不变的前提下,可以改变其内容。而不可改变是指,在内存引用地址不变的前提下,不可改变其内容,只能通过修改内存引用地址的方式,来修改内容。

​ 在JS中,对象和数组默认是可改变的,但Redux 期望所有状态更新都是使用不可变的方式。所以我们需要先复制原来的对象或数组,然后更新复制体,可结合展开运算符、或数组的相关方法实现。

const obj = {
  a: {
    c: 3
  },
  b: 2
}

const obj2 = {
  // obj 的备份
  ...obj,
  // 覆盖 a
  a: {
    // obj.a 的备份
    ...obj.a,
    // 覆盖 c
    c: 42
  }
}

const arr = ['a', 'b']
// 创建 arr 的备份,并把 c 拼接到最后。
const arr2 = arr.concat('c')
// 或者,可以对原来的数组创建复制体
const arr3 = arr.slice()
// 修改复制体
arr3.push('c')

三、react-redux

1、概念

​ react-redux 是 Redux 官方提供的 React 绑定库,简化了在 React 中使用 Redux 进行全局状态管理的操作。 通过 npm install react-redux ,就能将 react-redux 安装到项目中。使用时,首先按照redux的要求,创建好store、reducer、action等基础文件,然后从react-redux中引入provider、useSelector、useDispatch等工具来操作 redux。

2、Provider

​ 该组件用来直接包裹在根组件外,并将创建的 store 作为属性,这样就可在项目的任何地方访问 store ,而不必在每个组件引入 store。

// 项目入口文件 index.js
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './store'
import { Provider } from 'react-redux'
ReactDom.render(
  
    
  ,
  document.getElementById('root')
)

3、useSelector

​ 该工具是用来获取store中的公共数据,相当于 store.getState(),当关注的state发生变化时,会自动更新组件中的对应数据,引发页面重新渲染。

​ 语法:const n = useSelector((store的数据) => { return 你需要的部分 })

import React from 'react'
import { useSelector } from 'react-redux'

export default function Uncle () {
  // 获取数据
  // state:就是 store 中的全部数据
  const num = useSelector((state) => {
    // 返回我们需要的部分
    return state.num
  })
}

4、useDispatch

​ 该工具用来创建 dispatch,通过dispatch 派发 action,触发对应的 reducer,修改 store 中的state。

import React from 'react'

import { useDispatch, useSelector } from 'react-redux'

export default function Uncle () {
  // 创建 dispath
  const dispatch = useDispatch()
  // 派发 action  修改state
  dispath({ type: 'counter/increment' });
  // 获取数据
   // state:就是 store 中的全部数据
  const num = useSelector((state) => {
    // 返回我们需要的部分
    return state.num
  })
  // ....

四、redux-thunk

​ redux-thunk 是一个 中间件,执行时机是在 dispatch 发送 action 之后,到达 reducer 之前。也就是:dispatch(action) => 执行redux-thunk => reducer。redux-thunk 的作用主要是处理函数形式的action,以及执行函数形式的action中的异步操作代码,完成异步操作。

const action1 = async (dispatch) =>{
  const res = await 异步动作()
  dispatch({type: 'todos/add', payload: res.data})
}

dispatch(action1)

五、项目Redux相关的文件结构

首先下载依赖:
安装redux:              npm i redux
安装:                   npm i react-redux
thunk中间件:            npm i redux-thunk
查看redux的操作日志:     npm i redux-logger  

chrome开发者工具调试跟踪redux状态(需要先安装浏览器redux插件):
npm i redux-devtools-extension -D   

// 合并一次性下载
 npm i redux react-redux redux-thunk redux-devtools-extension

目录结构:
├── src
   ├── store                      # redux目录,一般约定叫store
   |     ├── index.js             # 定义并导出store. 其中会导入reducer
   |
   |     └── actions              # 多个模块的action
   |         ├── action1.js       # 模块1的 相关action creator
   |         ├── action2.js       # 模块2的 相关action creator
   |         └── index.js         # 合并  action creator
   |          
   |     └── reducers             # 多个模块的reducer
   |         ├── reducer1.js      # 模块1的reducer
   |         ├── reducer2.js      # 模块2的reducer
   |         └── index.js         # reducer的整体入口,会导入reducer1, reducer2
   |
   |     └── actionTypes
   |         ├── actionType1.js   # 模块1的actionType
   |         ├── actionType2.js   # 模块2的actionType
   |
   ├── index.js                   # 项目的入口文件,会导入并渲染App.js
   ├── App.js                     # 根组件,引入模块1 和模块2 组件
   |
   |── Pages 
         ├── 模块1.js       # 模块1 组件
         └── 模块2.js       # 模块2 组件

store/index.js:
// applyMiddleware(中间件1,中间件2)使用中间件
import { createStore, applyMiddleware } from 'redux'

// composeWithDevTools()  redux调试工具
import { composeWithDevTools } from 'redux-devtools-extension'

// thunk中间件  dispath()能够传入函数执行异步请求
import thunk from 'redux-thunk'

// 合并后的reducer
import reducer from './reducers'

// 创建store 传入合并后的reducer
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))

export default store
reducer/index.js:
// channel和newList组件的reducer
import channel from './channel'
import newList from './newList'

// combineReducers 合并各个reducer
import { combineReducers } from 'redux'

const rootReducer = combineReducers({
  channel,
  newList
})
export default rootReducer

项目入口文件 index.js:
import App from './App'
import ReactDOM from 'react-dom'
import './styles/index.css'

// Provider包裹根组件App就不需要每个组件都引入store了
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
  
    
  ,
  document.getElementById('root')
)

你可能感兴趣的:(React,react.js,学习,javascript)