react-redux 基础及新增 Hooks API —— useSelecter(), useStore(), useDispatch() 使用技巧
你今年被毕业了吗
前言
22年的新年刚过,23年刚打头,就给了一个大闷棍,最豪华毕业季,有多豪华,阵容有多大,一个20多近30人的部门,不声不响的就被干掉四分之三,大家没看错,设计,产品全被干掉了,后端被干掉了一大半,前端4个只剩下我一个,说好的疫情放开,市场回暖呢?
但是不管怎么样吧,日子还是要过的,该学的还是要学学,该记录的还是要记录的,俗话说的好:“兵来将挡,水来土掩”,边走边看吧。
以前没有写笔记的习惯,后面发现做了多年的开发,也没有留下点啥,希望在接下来的日子里,多多学习多多记笔记,希望给自己也给走在这条路上的小伙伴们一丢丢帮助,也给自己一点鼓励。加油,陌生人
。
不扯闲话,正式进入正题,Action
react-redux是什么
redux是一个独立专门用于做状态管理的JS库(不是react插件库)
它可以用在react、angular、vue等项目中,但基本与react配合使用
作用:集中式管理react应用中多个组件共享的状态
简单的说:react-redux 是基于 redux二次封装的库,提供了hooks相关的api。
使用场景
- 复杂大型项目
- 多组件状态共享
- 组件与组件之间的互相控制
- 组件嵌套过深,子组件需要控制祖先组件显示
基本原则
-
单一数据源
唯一数据源指的是应用的状态数据应该只存储在唯一的一个Store上。这个唯一Store上的状态,是一个树形的对象,每个组件往往只是用树形对象上一部分的数据,而如何设计Store上状态的结构,就是Redux应用的核心问题 -
State是只读的
保持状态只读,就是说不能去直接修改状态,要修改Store的状态,必须要通过派发一个action对象完成。改变状态的方法不是去修改状态上值,而是创建一个新的状态对象返回给Redux,由Redux完成新的状态的组装。 -
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。
核心是什么
Store
Store 是所有数据的源头,一个项目中只能有一个Store。
Store 就是把它们联系到一起的对象。Store 有以下职责:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
Reducer
Reducer 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 action 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
Action
通过action把数据传递给store,这是数据来源的有且唯一的方式。
其他功能
- getState 获取状态
store.getState()
// creatStore() 源码
export function createStore(reducer, preloadedState, enhancer) {
// ......
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
// .....
// getState() 源码, currentState 是 createStore() 传递进入的实参
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
// ......
}
-
dispatch 派发动作
这是触发状态更改的
唯一
方法,基本实现仅支持纯对象操作。如果你想发送一个Promise、一个Observable、一个thunk或其他东西,你需要将存储创建函数包装到相应的中间件中。
// action
export const updateNum = (payload) => {
return {
type: 'aaa',
payload
}
}
dispatch(updateNum('需要传递的值'))
// 或者
dispatch({type: '', data: {}})
// isPlainObject() 源码
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
// dispatch 源码
export function createStore(reducer, preloadedState, enhancer) {
// ......
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
// .....
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(
action
)}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
}
-
subscribe 订阅事件
添加更改监听事件,它将在dispatch()
被触发后任何时候被调用,并且状态树的某些部分可能已经改变。那么你可以调用store.getState()
读取回调中的当前状态树。可以使用以下命令从更改侦听器调用
dispatch()
注意:
- 订阅将在每次
dispatch()
调用之前进行快照。如果在调用侦听器时订阅或取消订阅不会对当前正在进行的dispatch()
产生任何影响。但是,下一个dispatch()
调用(无论是否嵌套)将使用订阅列表的最近快照。 - 侦听器不应期望看到所有状态更改,因为状态在之前嵌套的
dispatch()
期间可能已多次更新调用监听器。然而,保证所有用户在dispatch()
启动之前注册的,将使用最新的在退出时状态。
- 订阅将在每次
subscribe(() => {
const _STATE = getState();
console.log('订阅消息监听::', _STATE)
})
react-redux 新 API 之 HOOKS 使用技巧
以下所有的hooks都需要从react-redux中引入
import { connect, useDispatch, useSelector } from 'react-redux';
createStoreHook
createDispatchHook,
createSelectorHook
上面三个
useStore
const { subscribe, dispatch, getState, replaceReducer } = useStore();
// 获取仓库数据
const _STATE = getState();
// 触发仓库状态更新事件,与高阶connect函数 dispatch 和 useDispatch() 用法一样
<button onClick={() => dispatch(updateNum(storeNum + 1))}>点击更改数据++</button>
特别注意:
通过useStore()
获取的 getState()
与 useSelecter()
还是有一定的区别的, getState()
只会获得当前时刻的 redux state,之后state 更新并不会导致这个方法被再次调用,也不会导致重新渲染
。
使用场景区分:
- 假如当前组件需要监听 redux state 的变化,并根据 redux state的更新而渲染不同的视图或者有不同行为(
更新状态
), 那么就应该使用 Redux HooksuseSelector()
。 - 假如当前组件只是为了在 redux state中一次性查询某个数据状态,并不关心(或刻意忽略)之后的更新(
获取初始数据后,不更新状态
), 那么就应该使用useStore().getState()
。
useDispatch
const dispatch = useDispatch();
// update() 是action中定义的函数
<button onClick={() => dispatch(updateNum(storeNum + 1))}>点击更改数据++</button>
useSelector
useSelector()
接收一个函数,函数的默认参数是仓库所有redux state, 根据自己需要取出响应的数据/状态即可。
有个很重要的事情是,useSelecter() 是响应式
的,会自动订阅redux state
数据/状态,每次 redux state有更新,useSelector()
里的 selector 就会重新计算一次,返回新的结果
,并重新渲染当前组件
。这个与getState()
的一个重大区别也就是在这里。
// 具体使用
const storeNum = useSelector(state => state.num);
实现一个小demo
太过基础的就不再啰嗦,例如:创建一个基础react框架,下载依赖,创建文件夹,文件这些事情。能够来看这一类,相信前面所说的基础中的基础基本也都没啥问题,如果实在有问题,那么请再看看官网或者看看其他同学的相关笔记。
下面的demo就综合使用上诉所说的相关知识点,可能有些不伦不类,但希望坚持一下,因为我的目的是希望通过一个demo把相关知识点都运用一下,也有一个对比,最终项目中具体使用方案,全凭你自己做主。
目录结构
App根节点需要使用 Provider 包一下
// 引入store 入口文件
import store from '../Store';
// <Provider store={store}>
return (
<Provider store={store}>
<div className=''>store.getState()获取参数:{store.getState()['num']}</div>
<div className="app">
<LeftPage />
<CenterPage />
<RightPage />
</div>
</Provider>
);
store目录
store 仓库入口文件
// Provider, ReactReduxContext, batch, connect, createDispatchHook, createSelectorHook, createStoreHook, shallowEqual, useDispatch, useSelector, useStore
import { createStore, combineReducers } from 'redux';
// 引入state仓库
import STATE from './state';
// 引入初始数据和更新数据的reducer
import REDUCER from './reducer';
// 使用createStore的时候给画了一条线,看提示是要借助 @reduxjs/toolkit 才能实现,还没有去尝试
const store = createStore(combineReducers(REDUCER), STATE)
export default store;
state文件
const state = {
num: 0,
aaa: '11',
name: '张珊'
}
export default state;
action文件
export const updateNum = (payload) => {
return {
type: 'aaa',
payload
}
}
reducer文件
const reducer = {
// 此处的num就是仓库中定义的数据/状态关键字,个人理解是相当于对这个num所做的一个状态监听,如果数据发生变化话,则会触发此函数。
num: (state, action) => {
console.log('reducer中的参数::',state, action)
const { type, payload } = action;
switch (type) {
case 'aaa':
return payload
default:
// 这个地方没整明白,上面state打印结果为0(仓库中的值),但此处返回后,打印是null
// 只要仓库中初始值不为0,就能够正常输出结果。
// 此处仅限number类型,不考虑Boolean类型
return state === 0 ? 0 : state || null
}
}
}
export default reducer;
components目录
left.jsx
- 采用 connect 高阶函数
import React, { useEffect, useState } from "react";
import { connect } from 'react-redux';
import { updateNum } from '../Store/action'
const LeftPage = (props) => {
const { num, dispatchChangeNumFn } = props;
return (
<div>
<div>left</div>
<div>
<div style={{ fontSize: '30px' }}>{num}</div>
<button onClick={() => dispatchChangeNumFn(num + 1)}>点击更改数据++</button>
</div>
</div>
)
}
export default connect((state) => {
return state;
}, (dispatch) => {
return {
dispatchChangeNumFn: (payload) => {
dispatch(updateNum(payload))
}
}
})(LeftPage);
- 采用 hooks API
import React, { useEffect, useState } from "react";
import { connect, useDispatch, useSelector } from 'react-redux';
import { updateNum } from '../Store/action'
const LeftPage = () => {
const storeNum = useSelector(state => state.num);
const dispatch = useDispatch();
return (
<div>
<div>left</div>
<div>
<div style={{ fontSize: '30px' }}>{storeNum}</div>
<button onClick={() => dispatch(updateNum(storeNum + 1))}>点击更改数据++</button>
</div>
</div>
)
}
export default LeftPage;
center.jsx
import React, { useState } from "react";
import { useStore } from 'react-redux'
import { updateNum } from '../Store/action'
const CenterPage = () => {
console.log('store::', useStore())
const { subscribe, dispatch, getState, replaceReducer } = useStore()
const [_STATE, setState] = useState(getState())
// 消息订阅
subscribe(() => {
const _STATE = getState();
setState(_STATE)
console.log('订阅消息监听::', _STATE)
})
return (
<div>
<div>center</div>
<div>
<div>通过useStore()获取到的数据: {_STATE['num']}</div>
<button onClick={() => dispatch(updateNum(_STATE['num'] + 1))}>通过useStore()返回对象中的dispatch()更改数据++</button>
</div>
</div>
)
}
export default CenterPage;
right.jsx
import { useSelector } from 'react-redux'
const RightPage = () => {
return (
<div>
<div>right</div>
<div>
<div style={{ fontSize: '30px' }}>从左侧获取到的数据:{useSelector(state => state.num)}</div>
</div>
</div>
)
}
export default RightPage;
demo实现效果
redux流程
通过上面的基础介绍和demo,再借着下面这个图来仔细理解回味一下,这个redux的数据流是怎么样的。
redux数据流向总结
view ——> actions ——> reducer ——> state变化 ——> view变化(同步异步一样)
关于redux和react-redux的相关介绍,到此就算是结束了,如果还有疑问的请评论留言,如有错误之处,也请多多包涵并给与指正。
更多推荐
所有评论(0)