在这里插入图片描述

前言

22年的新年刚过,23年刚打头,就给了一个大闷棍,最豪华毕业季,有多豪华,阵容有多大,一个20多近30人的部门,不声不响的就被干掉四分之三,大家没看错,设计,产品全被干掉了,后端被干掉了一大半,前端4个只剩下我一个,说好的疫情放开,市场回暖呢?

但是不管怎么样吧,日子还是要过的,该学的还是要学学,该记录的还是要记录的,俗话说的好:“兵来将挡,水来土掩”,边走边看吧。

以前没有写笔记的习惯,后面发现做了多年的开发,也没有留下点啥,希望在接下来的日子里,多多学习多多记笔记,希望给自己也给走在这条路上的小伙伴们一丢丢帮助,也给自己一点鼓励。加油,陌生人

不扯闲话,正式进入正题,Action

react-redux是什么

redux是一个独立专门用于做状态管理的JS库(不是react插件库)
它可以用在react、angular、vue等项目中,但基本与react配合使用
作用:集中式管理react应用中多个组件共享的状态
简单的说:react-redux 是基于 redux二次封装的库,提供了hooks相关的api。

使用场景

  1. 复杂大型项目
  2. 多组件状态共享
  3. 组件与组件之间的互相控制
  4. 组件嵌套过深,子组件需要控制祖先组件显示

基本原则

  1. 单一数据源
    唯一数据源指的是应用的状态数据应该只存储在唯一的一个Store上。这个唯一Store上的状态,是一个树形的对象,每个组件往往只是用树形对象上一部分的数据,而如何设计Store上状态的结构,就是Redux应用的核心问题

  2. State是只读的
    保持状态只读,就是说不能去直接修改状态,要修改Store的状态,必须要通过派发一个action对象完成。改变状态的方法不是去修改状态上值,而是创建一个新的状态对象返回给Redux,由Redux完成新的状态的组装。

  3. 使用纯函数来执行修改

    为了描述 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,这是数据来源的有且唯一的方式。

其他功能

  1. 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
	 }

	// ......
}

  1. 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
	}
}

  1. subscribe 订阅事件
    添加更改监听事件,它将在dispatch()被触发后任何时候被调用,并且状态树的某些部分可能已经改变。那么你可以调用store.getState()读取回调中的当前状态树。

    可以使用以下命令从更改侦听器调用dispatch()

    注意:

    1. 订阅将在每次dispatch()调用之前进行快照。如果在调用侦听器时订阅或取消订阅不会对当前正在进行的dispatch()产生任何影响。但是,下一个dispatch()调用(无论是否嵌套)将使用订阅列表的最近快照。
    2. 侦听器不应期望看到所有状态更改,因为状态在之前嵌套的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 更新并不会导致这个方法被再次调用,也不会导致重新渲染

使用场景区分:

  1. 假如当前组件需要监听 redux state 的变化,并根据 redux state的更新而渲染不同的视图或者有不同行为(更新状态), 那么就应该使用 Redux Hooks useSelector()
  2. 假如当前组件只是为了在 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
  1. 采用 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);

  1. 采用 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的相关介绍,到此就算是结束了,如果还有疑问的请评论留言,如有错误之处,也请多多包涵并给与指正。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐