应用初始化时候,只请求一次数据,然后通过状态管理把数据存起来,需要数据的组件只需要从状态管理中‘拿’就可以了。
对于 SPA 单页面应用一切皆组件,对于嵌套比较深的组件,组件通信成了一个棘手的问题。如如下的场景, B 组件向 H 组件传递某些信息,那么常规的通信方式似乎难以实现。
这个时候状态管理就派上用场了,可以把 B 组件的信息传递给状态管理层,H 组件连接状态管理层,再由状态管理层通知 H 组件,这样就本质解决了组件通信问题。
state
只读 state
,来让状态发生变化,如果想要改变 state
,那就必须触发一次 action
,通过 action
执行每个 reducer
reducer
都是一个纯函数,里面不要执行任何副作用,返回的值作为新的 state
,state
改变会触发 store
中的 subscribe
Redux 可以作为发布订阅模式的一个具体实现。
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.dispatch
connect
有第一个参数,那么会将 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
还有 subscription
subscription
可以理解为订阅器 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
中,得到最新的 props
connect
都会产生一个新的 Subscription
,和父级订阅器建立起关联,这样父级会触发子代的 Subscription
来实现逐层的状态派发Subscription
通知的是 checkForUpdates
函数,checkForUpdates
会形成新的 props
,与之前缓存的 props
进行浅比较,如果不想等,那么说明 state
已经变化了,直接触发一个 useReducer
来更新组件,如果相等,那么当前组件不需要更新,直接通知子代 Subscription
,检查子代 Subscription
是否更新,完成整个流程redux-thunk
redux-saga
dvajs