在使用 react 的过程中,通常我们会通过 props 将父组件的一些数据传递到子组件,兄弟组件传递数据通过一个共同的父级,子传父可以通过回调函数来进行传递,当然这都是比较理想的情况,业务中往往不可能仅仅这样简单,最常见的一点就是跨很多层级的传递,你不可能一层层的通过 props 传递,这会让你的 props 变的异常臃肿不便
当然现实中,相信大多数人都会选择 react-redux,只要是 react 的项目肯定离不开 react-redux,它已然成为较为标准的 react 的状态管理框架,在横跨多个层级之间的状态共享、响应式变化方面起着尤为重要的作用
react 官方也提供了一些多层级传递的方式,像 context,它能够允许父组件向其下面的所有子组件提供信息,不需要 props 显式传递,举个例子,比如我们要共享登陆的用户数据,先创建一个 context
const UserContext = React.createContext();
export default UserContext;
然后在顶层引入这个导出的 context,使用 provider 包裹,被 provider 包裹的所有的组件包括所有子组件都可以享受到这个 value 值
<UserContext.Provider value={user}>
<div className="App">
<UserProfile />
</div>
</UserContext.Provider>
// 所有子组件都可以使用 useContext hook 进行获取数据
const user = useContext(UserContext);
这个数据从顶层保证了单一的数据源,如果需要修改,结合 react 当中的 reducer hook 进行数据的更改
那是不是这样就可以解决我们的问题了呢?表面上的问题是解决了,但是使用 context 会存在一些问题
一般 context 的应用场景是在主题颜色、当前登陆账户信息、路由等
既然 context 存在这样那样的问题,那有没有好一点的方式呢?那就是 redux
在讲 redux 之前,我们先了解一下 flux,为什么要先说 flux,主要原因是因为它是 redux 的鼻祖,可以说 redux 模仿的 flux 的架构思想,它们都有一个贯彻始终的思想,单向数据流
flux 单向数据流上面的图也表明了对应的流动关系,具体的过程就是,store 中保存了用到的具体的数据,store 发生变化的时候,就会导致 view 层的更新,如果 view 触发了一个 action,比如点击了某个事件,数据会发生一定的变化,view 会将这个 action 通过中央 dispatcher 传播到 store,store 本身已有对应的处理逻辑,处理对应的状态以及数据的变化,然后再触发 view 层的更新
那在这之前,传统的架构模式一般都是 MVC 架构,也就是模型、视图和控制器,模型(model)主要是负责应用程序中的数据和业务逻辑,视图(view)负责呈现数据以及用户界面,控制器(controller)则是负责协调模型和视图之间的交互,从这里其实就是可以看出,MVC 模式更加关注数据和业务逻辑的组织和管理,而 Flux 模式更加关注应用程序的数据流和状态管理,针对大型应用而言,Flux 的模式更适合处理数据,因为一切都是可控的。如果你用 MVC 的架构模式,每当添加一个新的功能,系统的复杂度就会疯狂增加
这种双向流动的数据,对于开发来说是难以接受的,很难理清其中的关系,并且当你修改其中的某一个内容的时候,影响点是无法准确评估的
既然 flux 是祖先,那为什么现在我们很少用 flux 呢?
首要的就是 flux 的学习成本较高,设计比较复杂,如果你要用 flux 的模式,你需要编写大量的代码,包括 Action、Dispatcher、Store 和 View 等组件,并且只适用于大型应用,小型应用很容易被这个复杂化的设计提升项目本身的难度,最主要的一点,业界已经有较好的方式,弥补了这些不足,比如我们要说的 redux,还有较好的 mobx,它们更简单,高效,易学习
既然 redux 比 flux 更优秀,那具体哪里优秀呢?或者它解决了 flux 上面的一些问题了么?首先单向数据流这个概念是不变的,在这个基础上,redux 还做了一些额外的能力
唯一数据源,flux 我们知道可以创建多个 store,但是这样导致的问题就是数据冗余,不同 store 之间又相互依赖增加了更多的复杂度,redux 的方式就是让整个应用使用一个 store,当然它不会阻止你创建多个
不能直接修改数据,改变只能靠纯函数,而纯函数就是 reducer
reducer(state, action) => newState
保证 reducer 是纯函数那就不应该直接改变原有的 state,而是返回一个新的 state,当传递相同的参数时,每次调用的返回结果应该是一致的,所以也要避免使用 Date.now() 或 Math.random() 这样的非纯函数,这样产生的结果是不可控的,针对不同的 action 在 reducer 函数内部处理,区分不同的 action 返回不同的 state,创建一个简单 reducer 类似下面这样,借用官网事例
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/incremented':
return { value: state.value + 1 }
case 'counter/decremented':
return { value: state.value - 1 }
default:
return state
}
}
state 的初始状态我们也给了对应的值,有了 reducer,我们需要创建一个 store,方式也很简单,通过 redux 提供的 createStore 进行创建,然后通过 subscribe 进行订阅,当 store 的数据发生变化的时候就会触发订阅的回调函数,改变内部状态的唯一方法是 dispatch 一个 action,代码如下
let store = createStore(counterReducer)
store.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'counter/incremented' })
// {value: 1}
可以看到,我们 dispatch 的 action 仅仅是通过 type 来描述我们干了什么,然后通过 reducer 返回一个新的 state,最后触发 订阅的回调函数,打印出来最新的 store 值
这个时候你会发现 redux 是可以独立使用的,也就是 react 和 redux 是两个独立的东西,你可以用 redux 而不用 react,如果两个真的要结合使用,可以用 react-redux 的库,会极大的简化代码,当然如果你了解了 redux 的原理,react-redux 也会轻松拿捏
功能有了,那如何实现这么一个简单的 redux 呢?其核心的内容也就是 createStore,然后暴露出来一系列的 subscribe、dispatch、getStore 等方法,用不到 20 行代码简单实现一下
function createStore(reducer) {
var state;
var listeners = []
function getState() {
return state
}
function subscribe(listener) {
listeners.push(listener)
return function unsubscribe() {
var index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listener => listener())
}
dispatch({})
return { dispatch, subscribe, getState }
}
简单解释一下
这段代码定义了一个名为 createStore 的函数,该函数接受一个 reducer 函数作为参数,并返回一个包含 dispatch、subscribe 和 getState 方法的对象。
getState 方法用于获取当前的状态值,subscribe 方法用于注册一个监听器,dispatch 方法用于执行某个操作并更新状态,并通知所有注册的监听器。
在函数内部,定义了一个 state 变量和一个 listeners 数组,用于存储状态和监听器。在 dispatch 方法中,执行 reducer 函数来更新状态,并遍历 listeners 数组,依次调用每个监听器。最后,调用 dispatch({}) 来初始化状态,并返回包含 dispatch、subscribe和getState 方法的对象
redux 还有较为强大的一点就是中间件,如果你对服务端相关的框架有一定的了解,对中间件肯定不会陌生。举个例子,如果你在每次 dispatch 相关内容的时候需要打一个日志,如果没有中间件你会这样做,借用官网的例子
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())
当然你可以进一步封装一个方法,使用这个方法就可以加上对应的日志
function dispatchAndLog(store, action) {
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())
}
// 调用
dispatchAndLog(store, action)
这样虽然可以,但是你需要在任何使用 log 的地方都需要引入这个函数,并且如果有多个 middleware 使用还是不方便,能不能提供一种可以组合多个 middleware 的方式呢?中间省略了 n 个过程,对整个过程感兴趣的可以阅读官网整个流程,理解middleware
// 假设有这样一个 middleware,返回一种新的 dispatch
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
// 可以组合任意 middleware
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
let dispatch = store.dispatch
middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)))
return Object.assign({}, store, { dispatch })
}
当然,redux 给你提供了立即可用的 applyMiddleware 直接组合你的中间件
有了 redux,要把 react 和 redux 进行较好的结合,就像刚开始提到的,如果仅仅是数据传递,使用 context 之后会导致额外的一些性能问题都需要手动处理,但是 react-redux 在内部实现了许多性能优化,以便你编写的组件仅在实际需要时重新渲染,并且使用一些 hook 的形式极大简化了我们的代码和逻辑,如果你要在 react 项目中使用 redux,那就 @reduxjs/toolkit react-redux
之前我们没说到 redux toolkit,redux 我们也看到了,在实际业务中编写 reducer 又臭又长,而 toolkit 就是在 redux 的基础上能够简化了大多数 Redux 任务,避免了常见错误,使得编写 Redux 应用程序更容易了,可以把它称为 redux 的最佳实践
redux 是一个 JavaScript 状态容器,用于管理应用程序状态。redux 的三个原则:单一数据源、状态是只读的、使用纯函数来执行状态更改。文章描述了如何应用它们以及它们的好处。redux 的核心概念包括 store、action、reducer 和 middleware。redux 使用 action 来描述状态更改,reducer 根据 action 来更新状态,而 middleware 则用于处理异步操作和副作用
redux toolkit是一个官方推荐的 redux 工具集,它提供了一些简化 redux 开发的工具和 API,例如 createSlice、createAsyncThunk 和 createEntityAdapter 等。使用 redux toolkit 可以更容易地编写可维护和可扩展的 redux 代码,并减少样板代码的数量