Redux的作用是什么
Redux
的作用在于实现状态传递
、状态管理
。在这里你可能会说了,如果是状态传递
,那我props
的传递不也是可以达到这样的效果吗?context上下文方案
不也是可以达到这样的效果吗?没错,是这样的,但是上述的两种方案是有局限性
的。
props
方案只适用于父子组件传递状态。context
上下文方案虽然能够在根组件上定义上下文,但是有两种缺陷- 只要上下文里面的状态发生改变,就会重新渲染整个组件树,进而会产生庞大的性能开销。
- 组件的逻辑与状态的
耦合度
太高,不利于解耦,也就是无法实现对状态的统一管理。
既然Redux
的作用是对状态的管理与传递,那么他的作用场景呢?当然了你可以根据上面说的两种方案对Redux
的使用做取舍,Redux
的本质就是全局变量被协调管理。
- 如果涉及多个状态,且多个状态会被多个组件使用到,比如商城购物车场景,你就可以毫不犹豫的考虑用
Redux
。 - 如果涉及多个状态,但是状态虽多但是是用的组件唯一,或者有关联关系的组件使用,你就大可不必使用
Redux
,如果状态不是那么多,那就更不必使用Redux
了。
除此之外,Redux
还有一个优点就是,不仅仅是React
本身能够使用,就连别的框架,比如jQuery
、kerry_dom
、vue
等都可以使用,但是对比于vue
来讲的话,vue有自己比较好的的状态管理库vuex
,好了废话不多说了,我们先来看看Redux
在项目中是如何是用的。
Redux的使用
// store.js
import { createStore } from "redux";
import reducer from "./reducer";
export default createStore(reducer);
// reducer.js
import {cloneDeep} from 'lodash';
const initilaValue = {
count: 0
};
const reducer = (state = initilaValue, action) => {
state = cloneDeep(state)
const { type, payload } = action;
switch (type) {
case "add":
state.count += payload;
break;
case "reduce":
state.count -= payload;
break
default:
}
return state;
};
export default reducer;
// App.js
import React, {Component} from 'react';
import store from "./store";
export default class App extends Component {
componentDidMount() {
//reducer不会触发页面变化,需要state来触发
store.subscribe(() =>{
this.setState(store.getState())
})
}
render() {
//获取reducer数据
const {count} = store.getState()
return (
<div>
<div type='primary' onClick={this.reduce}>-</div>
<span>{count}</span>
<div type='primary' onClick={this.add}>+</div>
</div>
);
}
reduce = () => {
//通知reducer页面数据变化了
store.dispatch({
type: 'reduce',
payload: 1
})
}
add = () => {
//通知reducer页面数据变化了
store.dispatch({
type: 'add',
payload: 1
})
}
}
上述代码就可以实现count
的加减计算了,我们可以看到有几处需要注意的地方。
store.js
文件里面的createStore
。reducer.js
文件里面的cloneDeep
、return state
、state = initialValue
。App.js
文件里面的dispatch
、getState
、type
、payload
。
很明显createStore
的作用就是创建仓库,getState
为取得当前的state
值,dispatch
为某个操作之后派发给store
去更新某个state
,type
为具体的某种交互,payload
为每次交互的具体内容。各位同学可以看得到我在reducer
中做了一次state
的深克隆,这是为什么呢?是因为在每一次的action
中我们拿到的是同一个state
的内存地址
,我们的期望是不管你在switch
中如何更改state
但是我不希望在这一步就改变了公共状态中的count
,只有在我return
的时候才会去更改真正的公共状态,也就是说reducer
函数执行产生的私有闭包里面的公共状态信息。而state = initialValue
这一步的操作就是第一次派发的时候,reducer
接收的state
为空,我们把基础值赋给它。了解了这么多,我们还是去看一下他的源码是如何实现的吧。
Redux的源码
//Redux/redux/src/index.ts
export {
createStore, // 创建仓库api
combineReducers, // 合并Reducer
bindActionCreators, // 转化action
applyMiddleware, // 中间件应用方案
compose,// 函数组合
__DO_NOT_USE__ActionTypes
}
上面的index.ts
文件夹里面暴露出了几个api
,我们主要针对createStore
看看。
export default function createStore(
reducer: Reducer<S, A>, preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>, enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
// createStore函数不支持第二三四参数为函数
// 详见https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
...
)
}
// 第二参数是函数,且第三参数不传
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}
// 有第三参数且不是函数
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf( enhancer )}'`
)
}
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
// 如果reducer不是函数,要报错
if (typeof reducer !== 'function') {
throw new Error(
`Expected the root reducer to be a function. Instead, received: '${kindOf( reducer )}'`
)
}
let currentReducer = reducer // 当前的reducer
let currentState = preloadedState as S // 当前的state
let currentListeners: (() => void)[] | null = [] // 事件池
let nextListeners = currentListeners
let isDispatching = false // 正在派发
/** * This makes a shallow copy of currentListeners so we can use * nextListeners as a temporary list while dispatching. * * This prevents any bugs around consumers calling * subscribe/unsubscribe in the middle of a dispatch. */
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// 返回当前的state
function getState(): S {
if (isDispatching) {
throw new Error(
...
)
}
return currentState as S
}
//向事件池中添加更新事件
function subscribe(listener: () => void) {
// 校验是不是函数
if (typeof listener !== 'function') {
throw new Error(
...
)
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
let isSubscribed = true
//避免重复添加
ensureCanMutateNextListeners()
nextListeners.push(listener)
//subscribe函数每执行一次,都会返回一个unsubscribe用来从事件池中移除当前事件
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
...
)
}
isSubscribed = false
ensureCanMutateNextListeners()
// 移除当前事件
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
// 派发函数
function dispatch(action: A) {
// 如果传过来的action不是对象报错
if (!isPlainObject(action)) {
throw new Error(
...
)
}
//每一个action中都需要一个type字段,没有就报错
if (typeof action.type === 'undefined') {
throw new Error(
...
)
}
//正在派发中..
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 执行reducer,改变state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// dispatch通知事件池去执行事件,遍历执行
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// 替换replaceReducer
function replaceReducer<NewState, NewActions extends A>(
nextReducer: Reducer<NewState, NewActions>
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
// 入参不是函数,报错
if (typeof nextReducer !== 'function') {
throw new Error(
...
)
}
...
// 刚开始进来要派发一次,同步state,其中{ type: ActionTypes.REPLACE }为唯一标识
// 倘若不为唯一标识的话,那可能一开始就破坏了状态value
dispatch({ type: ActionTypes.REPLACE } as A)
// change the type of the store by casting it to the new store
return store as unknown as Store<
ExtendState<NewState, StateExt>,
NewActions,
StateExt,
Ext
> &
Ext
}
...
// 初始化store的时候,需要派发一次同步state
dispatch({ type: ActionTypes.INIT } as A)
const store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
// 返回仓库 const store = createStore({count:0})
return store
}
确实短短几百行代码实现了redux
,为此我们也来实现一个简易版的redux
表示敬意,我们的redux
只实现getState
、dispatch
、createStore
方法。
// myRedux
import {cloneDeep} from 'lodash'
export function createStore(reducer) {
if(typeof reducer !== 'function') {
throw new Error('reducer must be an function')
}
let state,
listeners = [];
const getState = () => {
// 深克隆一个state
return cloneDeep(state);
}
const subscribe = (listener) => {
if(typeof listener !== 'function'){
throw new Error('listener must be an function')
}
//去重
if(!listeners.includes(listener)){
listeners.push(listener)
}
// 源码里面执行subscribe,返回一个卸载函数
return function unsubscribe(){
listeners.filter(action => action!== listener)
}
}
const dispatch = (action) => {
if(typeof action !== 'object' && action !== null){
throw new Error('action must be a object')
}
// 判断有没有type
if(typeof action.type === undefined){
throw new Error('action.type must existence')
}
//执行
try {
state = reducer(state, action)
} catch (error) {
//...
}
// 通知事件池中的方法执行
listeners.forEach(listener=>{
if(typeof listener === 'function'){
listener();
}
})
}
//第一次进来要派发一次,同步初始状态
dispatch({
type:typeof Symbol !== undefined ? Symbol('ABC') : '唯一值'
})
// 暴露出方法
return {
getState,
dispatch,
subscribe
}
}
参考 前端进阶面试题详细解答
各位可以去codeSandBox上面去实践一下,当然了这只是简易版的redux
,官方推荐使用react-redux来进行实际的项目开发,因为他只关注于数据管理。
总结
redux
的大致工作流如此: