
应用初始化时候,只请求一次数据,然后通过状态管理把数据存起来,需要数据的组件只需要从状态管理中‘拿’就可以了。

对于 SPA 单页面应用一切皆组件,对于嵌套比较深的组件,组件通信成了一个棘手的问题。如如下的场景, B 组件向 H 组件传递某些信息,那么常规的通信方式似乎难以实现。
这个时候状态管理就派上用场了,可以把 B 组件的信息传递给状态管理层,H 组件连接状态管理层,再由状态管理层通知 H 组件,这样就本质解决了组件通信问题。



state 只读 state ,来让状态发生变化,如果想要改变 state ,那就必须触发一次 action ,通过 action 执行每个 reducerreducer 都是一个纯函数,里面不要执行任何副作用,返回的值作为新的 state ,state 改变会触发 store 中的 subscribeRedux 可以作为发布订阅模式的一个具体实现。
Redux 都会创建一个 store ,里面保存了状态信息,改变 store 的方法 dispatch ,以及订阅 store 变化的方法 subscribe 。
Redux 应用了前端领域为数不多的中间件 compose ,Redux 的中间件用来强化 dispatch , Redux 提供了中间件机制,使用者可以根据需要来强化 dispatch 函数,传统的 dispatch 是不支持异步的,但是可以针对 Redux 做强化,于是有了 redux-thunk,redux-actions 等中间件,包括 dvajs 中,也写了一个 Redux 支持 promise 的中间件。
const compose = (...funcs) => {
return funcs.reduce((f, g) => (x) => f(g(x)))
}
funcs 为中间件组成的数组,compose 通过数组的 reduce 方法,实现执行每一个中间件,强化 dispatch。
createStore
createStore 可以创建一个 Store ,使用者可以将这个 Store 保存传递给 React 应用const store = createStore(reducer, initialState, middleware)reducer 是一个纯函数,用来处理 action ,返回新的 state reducer ,可以使用 combineReducers 来合并initialState 是初始状态middleware,如果有中间件,可以在这里传入combineReducers
const rootReducer = combineReducers({
count: countReducer,
user: userReducer,
})
applyMiddleware
const middleware = applyMiddleware(logMiddleware)复制🤏
applyMiddleware,用于注册中间件,可以将多个中间件组合成一个中间件action 中间件依次执行编写 reducer
function countReducer(state = 0, action) {
switch (action.type) {
case "ADD":
return state + 1
case "MINUS":
return state - 1
default:
return state
}
}
function InfoReducer(state = {}, action) {
const { payload = {} } = action
switch (action.type) {
case "SET":
return { ...state, ...payload }
default:
return state
}
}
注册中间件
function logMiddleware() {
return (next) => {
return (action) => {
const { type } = action
console.log("dispatch ", type)
return next(action)
}
}
}
生成 Store
const rootMiddleware = applyMiddleware(logMiddleware)
const rootReducer = combineReducers({
count: countReducer,
info: InfoReducer,
})
const Store = createStore(
rootReducer,
{
count: 0,
info: {
name: "",
age: 18,
},
},
rootMiddleware
)
使用 Redux
function Index() {
const [state, changeState] = useState(Store.getState())
useEffect(() => {
const unSubscribe = Store.subscribe(() => {
changeState(Store.getState())
})
return () => {
unSubscribe()
}
}, [])
return (
<div>
<h1>{state.count}</h1>
<button onClick={() => Store.dispatch({ type: "ADD" })}>+</button>
<button onClick={() => Store.dispatch({ type: "MINUS" })}>-</button>
<p>{state.info.name}</p>
<p>{state.info.age}</p>
<button
onClick={() => Store.dispatch({ type: "SET", payload: { name: "cellin", age: 18 } })}
>
set info
</button>
</div>
)
}
存在的问题:
subscribe / unSubscribe 来进行订阅A 组件需要状态 a,B 组件需要状态 b ,那么改变 a,只希望 A 组件更新,不希望 B 组件更新,显然上述是不能满足的所以为了解决上述诸多问题,React-Redux 就应运而生了。
React-Redux 是沟通 React 和 Redux 的桥梁,它主要功能体现在如下两个方面:
Redux 的 Store,并把它合理分配到所需要的组件中Store 中 state 的改变,促使消费对应的 state 的组件更新由于 Redux 数据层,可能被很多组件消费,所以 React-Redux 中提供了一个 Provider 组件,可以全局注入 Redux 中的 store ,所以使用者需要把 Provider 注册到根部组件中。
Provider 作用就是保存 Redux 中的 store ,分配给所有需要 state 的子孙组件。
export default function Root() {
return (
<Provider store={store}>
<App />
</Provider>
)
}
React-Redux 提供了一个高阶组件 connect ,被 connect 包装后组件将获得如下功能:
props 中获取改变 state 的方法 Store.dispatchconnect 有第一个参数,那么会将 Redux state 中的数据,映射到当前组件的 props 中,子组件可以使用消费state ,有变化的时候,会通知当前组件更新,重新渲染视图可以利用 connect 提供的功能,做数据获取,数据通信,状态派发等操作。
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)复制🤏
mapStateToProps
state,映射到业务组件的 props 中,state 改变触发,业务组件 props 改变,触发业务组件更新视图store 的改变const mapStateToProps = (state) => ({ count: state.count })
mapDispatchToProps
dispatch 方法,映射到业务组件的 props 中const mapDispatchToProps = (dispatch) => ({
addCount: () => dispatch({ type: "ADD" }),
setInfo: (payload) => dispatch({ type: "SET", payload }),
})
mergeProps
正常情况下,如果没有这个参数,会按照如下方式进行合并,返回对象
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...stateProps,
...dispatchProps,
...ownProps,
})
可以自定义的合并规则,还可以附加一些属性
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...stateProps,
...dispatchProps,
...ownProps,
name: "cellin",
})
options
{
context?: Object,
pure?: boolean,
areStatesEqual?: Function,
areOwnPropsEqual?: Function,
areStatePropsEqual?: Function,
areMergedPropsEqual?: Function,
forwardRef?: boolean,
}
通过在根组件中注入 store ,并在 useEffect 中改变 state 内容
export default function Root() {
useEffect(() => {
Store.dispatch({ type: "ADD" })
Store.dispatch({ type: "SET", payload: { name: "cellin", age: 18 } })
}, [])
return (
<Provider store={Store}>
<Index />
</Provider>
)
}
在整个应用中在想要获取数据的组件里,获取 state 中的内容
import { connect } from "react-redux"
class Index extends React.Component {
componentDidMount() {
console.log(this.props)
}
render() {
const { info, number } = this.props
return (
<div>
<h1>{number}</h1>
<p>{info.name}</p>
<p>{info.age}</p>
</div>
)
}
}
const mapStateToProps = (state) => ({
number: state.count,
info: state.info,
})
export default connect(mapStateToProps)(Index)
通过 mapStateToProps 获取指定 state 中的内容,然后渲染视图
function ComponentA({ toCompB, compBSend }) {
const [compASend, setCompASend] = useState("")
return (
<div>
<p> CompA </p>
<p>from CompB: {compBSend}</p>
<input type="text" value={compASend} onChange={(e) => setCompASend(e.target.value)} />
<button onClick={() => toCompB(compASend)}>send to CompB</button>
</div>
)
}
const compAMapStateToProps = (state) => ({
compBSend: state.compBSend,
})
const compAMapDispatchToProps = (dispatch) => ({
toCompB: (msg) => dispatch({ type: "SET", payload: { compASend: msg } }),
})
export const CompA = connect(compAMapStateToProps, compAMapDispatchToProps)(ComponentA)
class ComponentB extends React.Component {
state = { compBSend: "" }
handleToA = () => {
this.props.dispatch({
type: "SET",
payload: { compBSend: this.state.compBSend },
})
}
componentDidMount() {
console.log(this.props)
}
render() {
const { compASend } = this.props
return (
<div>
<p> CompB </p>
<p>from CompA: {compASend}</p>
<input
type="text"
value={this.state.compBSend}
onChange={(e) => this.setState({ compBSend: e.target.value })}
/>
<button onClick={this.handleToA}>send to CompA</button>
</div>
)
}
}
const compBMapStateToProps = (state) => ({
compASend: state.compASend,
})
export const CompB = connect(compBMapStateToProps)(ComponentB)
/* react-redux/src/components/Provider.js */
const ReactReduxContext = React.createContext(null)
function Provider({ store, context, children }) {
const contextValue = useMemo(() => {
const subscription = new Subscription(store)
return {
store,
subscription,
}
}, [store])
useEffect(() => {
const { subscription } = contextValue
subscription.trySubscribe()
return () => {
subscription.tryUnsubscribe()
}
}, [contextValue])
const Context = ReactReduxContext
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
context 上下文来保存传递 Store 的,但是上下文 value 保存的除了 Store 还有 subscriptionsubscription 可以理解为订阅器 state 变化,另一方面通知对应的组件更新Provider 的 useEffect 中,进行真正的绑定订阅功能,其原理内部调用了 store.subscribe ,只有根订阅器才会触发 store.subscribe/* react-redux/src/utils/Subscription.js */
export default class Subscription {
constructor(store, parentSub) {}
addNestedSub(listener) {
this.trySubscribe()
return this.listeners.subscribe(listener)
}
notifyNestedSubs() {
this.listeners.notify()
}
isSubscribed() {
return Boolean(this.unsubscribe)
}
trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.handleChangWrapper)
: this.store.subscribe(this.handleChangeWrapper)
this.listeners = createListenerCollection()
}
}
tryUnsubscribe() {}
}
整个订阅器的核心:层层订阅,上订下发
connect 包装的组件,内部也有一个 Subscription ,而且这些订阅器一层层建立起关联,Provider 中的订阅器是最根部的订阅器,可以通过 trySubscribe 和 addNestedSub 方法可以看到connect ,子孙组件也有 connect ,那么父子 connect 的 Subscription 也会建立起父子关系trySubscribe 的时候,能够看到订阅器会和上一级的订阅器通过 addNestedSub 建立起关联store 中 state 发生改变,会触发 store.subscribe ,但是只会通知给 Provider 中的根 Subscription,根 Subscription 也不会直接派发更新,而是会下发给子代订阅器( connect 中的 Subscription ),再由子代订阅器,决定是否更新组件,层层下发 
connect 中有一个 selector 的概念,他通过 mapStateToProps ,mapDispatchToProps ,把 Redux 中 state 状态合并到 props 中,得到最新的 propsconnect 都会产生一个新的 Subscription ,和父级订阅器建立起关联,这样父级会触发子代的 Subscription 来实现逐层的状态派发Subscription 通知的是 checkForUpdates 函数,checkForUpdates 会形成新的 props ,与之前缓存的 props 进行浅比较,如果不想等,那么说明 state 已经变化了,直接触发一个 useReducer 来更新组件,如果相等,那么当前组件不需要更新,直接通知子代 Subscription ,检查子代 Subscription 是否更新,完成整个流程redux-thunkredux-sagadvajs