From 39932f6ed64ee8980d4f5e26c6b0f5d8ca63b2ef Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 18 Aug 2023 09:05:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20redux=5F=E5=AD=A6=E4=B9=A0=E7=AC=94?= =?UTF-8?q?=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...23-08-04-redux\347\254\224\350\256\260.md" | 519 ++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 "_posts/2023-08-04-redux\347\254\224\350\256\260.md" diff --git "a/_posts/2023-08-04-redux\347\254\224\350\256\260.md" "b/_posts/2023-08-04-redux\347\254\224\350\256\260.md" new file mode 100644 index 00000000000..ecc26b3c3b4 --- /dev/null +++ "b/_posts/2023-08-04-redux\347\254\224\350\256\260.md" @@ -0,0 +1,519 @@ +--- +layout: post +title: Redux学习 +subtitle: 官方文档,store +date: 2023-08-18 +author: forwardZ +header-img: img/the-first.png +catalog: false +tags: + - 杂谈 +--- + +# 总结: +1,state 是需要组织的,考虑如同数据库一样对待 +2,redux 数据流是 组织流程的一种方式,保证 可追溯,不突变 +3,中间件相关,logger 是中间件的一种,注意其中链式调用的实现, +中间件是为了实现异步action,由于reducer只能处理纯函数,不能处理effect,异步的内容需放在dispatch的环节前, +通过中间件,action创建函数除了可以返回 action 对象,还可以返回函数。 + +# 1,介绍 + +## 1.1 动机 +JavaScript 需要管理比任何时候都要多的 state (状态) +Redux 试图让 state 的变化变得可预测。 + +## 1.2 核心概念 + +reducer 只是一个接收 state 和 action,并返回新的 state 的函数。 +在大型应用中,开发一个 reducer 调用这多个 reducer,进而来管理整个应用的 state + +## 1.3 三大原则 + +### 1, 单一数据源 + +整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。 + +### 2, State 是只读的 +唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。 + +### 3, 使用纯函数来执行修改 + +即通过 reducers,来改变 state tree +为了描述 action 如何改变 state tree ,你需要编写 reducers。 + +## 1.4 先前技术(略) + +## 1.5 生态系统(略) + +## 1.6 示例,TODO + + +# 2,基础 + +## 2.1 action + +Action 是把数据从应用(译者注:这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store + +Action 本质上是 JavaScript 普通对象。约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量 + +除了 type 字段外,action 对象的结构完全由你自己决定 + +我们应该尽量减少在 action 中传递的数据 + + +## 2.2 Reducer + +Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的 + +### 1, 设计state的结构 +在 Redux 应用中,所有的 state 都被保存在一个单一对象中。 +1)最简的形式 +2)尽量与UI的state分开 +3)开发复杂的应用时,不可避免会有一些数据相互引用。建议尽可能地把 state 范式化,不存在嵌套。 + +### 2,action的处理 +reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。 +永远不要在 reducer 里做这些操作: +* 修改传入参数; +* 执行有副作用的操作,如 API 请求和路由跳转; +* 调用非纯函数,如 Date.now() 或 Math.random()。 +我们将以指定 state 的初始状态作为开始。Redux 首次执行时,state 为 undefined,此时我们可借机设置并返回应用的初始 state。 +注:createStore传入 reducer时,会执行 reducer,此时 state 为 undefined,可借机设置并返回应用的初始 state。 + +注意: +1,不要修改 state +2,在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。 + +### 3,处理多个 action +我们需要修改数组中指定的数据项而又不希望导致突变,(Javascript中的对象存储时均是由值和指向值的引用两个部分构成。此处突变指直接修改引用所指向的值, 而引用本身保持不变。) + +### 4,拆分 Reducer +```js +//... +function visibilityFilter(state = SHOW_ALL, action) { + switch (action.type) { + case SET_VISIBILITY_FILTER: + return action.filter + default: + return state + } +} + +function todoApp(state = {}, action) { + return { + visibilityFilter: visibilityFilter(state.visibilityFilter, action), + todos: todos(state.todos, action) + } +} +// combineReducers() 所做的只是生成一个函数, +// 这个函数来调用你的一系列 reducer, +// 每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理, +// 然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象 +import { combineReducers } from 'redux' + +const todoApp = combineReducers({ + visibilityFilter, + todos +}) +``` +注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。 + +## 2.3 Store +Store 就是把action,reducer联系到一起的对象 +1,维持应用的 state; +2,提供 getState() 方法获取 state; +3,提供 dispatch(action) 方法更新 state; +4,通过 subscribe(listener) 注册监听器; +5,通过 subscribe(listener) 返回的函数注销监听器。 + +注: +Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。 + +```js +const action = (dispatch,getState) => { + +} +store.dispatch(action) // 实际这里会执行action,并传入state +// action(dispatch,getStates) + +``` + +## 2.4 数据流 +严格的单向数据流是 Redux 架构的设计核心。 +Redux 应用中数据的生命周期遵循下面 4 个步骤 +1,调用 store.dispatch(action)。 +在任何地方调用 store.dispatch(action),包括组件中、XHR 回调中、甚至定时器中。 +2,Redux store 调用传入的 reducer 函数。 +Store 会把两个参数传入 reducer: 当前的 state 树和 action。 +3,根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。 +当你触发 action 后,combineReducers 返回的 todoApp 会负责调用两个 reducer +ps:所有的都会调用 +4,Redux store 保存了根 reducer 返回的完整 state 树。 +这个新的树就是应用的下一个 state!所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。 + +现在,可以应用新的 state 来更新 UI。如果你使用了 React Redux 这类的绑定库,这时就应该调用 component.setState(newState) 来更新。 + + +# 3,高级 + +## 3.1,异步action +### 1,action +当调用异步 api 时,有两个非常关键的时刻:发起请求的时刻,和接收到响应的时刻(也可能是超时)。 +这两个时刻都可能会更改应用的 state;为此,需要 dispatch 普通的同步 action。 +一般情况下,每个 API 请求都需要 dispatch 至少三种 action: +1,一种通知 reducer 请求开始的 action。 +对于这种 action,reducer 可能会切换一下 state 中的 isFetching 标记。以此来告诉 UI 来显示加载界面。 + +2,一种通知 reducer 请求成功的 action。 +对于这种 action,reducer 可能会把接收到的新数据合并到 state 中,并重置 isFetching。UI 则会隐藏加载界面,并显示接收到的数据 + +3,一种通知 reducer 请求失败的 action +对于这种 action,reducer 可能会重置 isFetching。另外,有些 reducer 会保存这些失败信息,并在 UI 里显示出来。 + +### 2,同步 Action 创建函数(Action Creator) + +两类action: +1,用户控制的action, +2,网络请求控制的action, + +### 3,设计 state 结构 +考虑数据库的做法, + +### 4,处理 Action + +### 5,异步 action 创建函数 +将同步 action 和 网络请求 结合,标准做法是:使用 redux thunk 中间件 +通过使用中间件,action 创建函数除了可以返回 action 对象,还可以返回函数。此时,该 action创建函数 就成为了 thunk 。 +action创建函数 返回的函数,可以被 中间件执行,该函数: +1,不需要保证纯净,可以带有 副作用,如异步请求 +2,还可以 dispatch action + +```js +import fetch from 'cross-fetch' + +export const REQUEST_POSTS = 'REQUEST_POSTS' +function requestPosts(subreddit) { + return { + type: REQUEST_POSTS, + subreddit + } +} + +// requestPosts() ===> 返回 action 对象,action 创建函数 + +export const RECEIVE_POSTS = 'RECEIVE_POSTS' +function receivePosts(subreddit, json) { + return { + type: RECEIVE_POSTS, + subreddit, + posts: json.data.children.map(child => child.data), + receivedAt: Date.now() + } +} + +// receivePosts() ===> 返回 action 对象,action 创建函数 + +// 来看一下我们写的第一个 thunk action 创建函数! +// 虽然内部操作不同,你可以像其它 action 创建函数 一样使用它: +// store.dispatch(fetchPosts('reactjs')) ::// 允许 dispatch 函数 + +fetchPosts(subreddit) ===> fn +fn() ===> dispatch() && fetch() +fetch() ===> dispatch() + +// 该action创建函数 返回的是函数,而不是 action 对象 +// 通过中间件,可以直接 dispatch 返回函数的action创建对象 +// 一般返回函数的action 创建函数,里面会执行 dispatch,且一般执行多个 dispatch +export function fetchPosts(subreddit) { + // Thunk middleware 知道如何处理函数。 + // 这里把 dispatch 方法通过参数的形式传给函数,函数内部也可以 dispatch action + return function (dispatch) { + // 首次 dispatch:更新应用的 state 来通知 + // API 请求发起了。 + dispatch(requestPosts(subreddit)) + // thunk middleware 调用的函数可以有返回值, + // 它会被当作 dispatch 方法的返回值传递。 + + // 这个案例中,我们返回一个等待处理的 promise。 + // 这并不是 redux middleware 所必须的,但这对于我们而言很方便。 + + return fetch(`http://www.subreddit.com/r/${subreddit}.json`) + .then( + response => response.json(), + // 不要使用 catch,因为会捕获在 dispatch 和渲染中出现的任何错误,导致 'Unexpected batch number' 错误。 + // https://github.com/facebook/react/issues/6895 + error => console.log('An error occurred.', error) + ) + .then(json => + // 可以多次 dispatch! + // 使用响应数据,来更新应用的 state。 + dispatch(receivePosts(subreddit, json)) + ) + } +} + + +``` + + +thunk 的一个优点是它的结果可以再次被 dispatch: + +```js + +import thunkMiddleware from 'redux-thunk' +import { createLogger } from 'redux-logger' +import { createStore, applyMiddleware } from 'redux' +import { selectSubreddit, fetchPosts } from './actions' +import rootReducer from './reducers' + +const loggerMiddleware = createLogger() + +const store = createStore( + rootReducer, + /* 中间件是作为参数传给 applyMiddleware 的,applyMiddleware 则是redux 导出的 */ + applyMiddleware( + thunkMiddleware, // 允许我们 dispatch() 函数 + loggerMiddleware // 一个很便捷的 middleware,用来打印 action 日志 + ) +) + +store.dispatch(selectSubreddit('reactjs')) +// dispatch 后仍可被 dispatch,链式调用 +store + .dispatch(fetchPosts('reactjs')) + .then(() => console.log(store.getState()) +) +``` + +这里的代码需手写下 + +除了 thunk-middleware,还有其他中间件 +* 可以使用 redux-promise 或者 redux-promise-middleware 来 dispatch Promise 来替代函数 +* 可以使用 redux-observable 来 dispatch Observable +* 可以使用 redux-saga 中间件来创建更加复杂的异步 action +* 可以使用 redux-pack 中间件 dispatch 基于 Promise 的异步 Action +* 甚至可以写一个自定义的 middleware 来描述 API 请求,如示例中 real-world + + +## 3.2 异步数据流 +默认情况下,createStore() 创建的 store 并没有使用 中间件,所以只支持 同步数据流 +一般支持异步的中间件,都包装了 store 的 dispatch 方法,可以用来 dispatch action以外 的其他内容,如 函数 或者 promise。 +(ps:所谓包装,即重写了 dispatch 函数,比如先保存dispatch,再执行其他操作,最后dispatch操作) + +任何 middleware 都可以以自己的方式解析 dispatch 的内容,并传递 action 给下一个 middleware。如,支持 promise 的middleware 能拦截 promise ,并为每个 promise 异步dispatch 一对 begin/end actions +(ps:promise.then,有不同的阶段) + +重点: +当 middleware 链中最后一个 middleware 开始 dispatch action 时,该 action 必须是一个普通对象,这是同步redux数据流的开始的地方。 +补充: +可以使用任意多异步的 middleware 去做你想做的事情,但是需要使用普通对象作为最后一个被 dispatch 的 action ,来将处理流程带回同步方式 + +## 3.3 middleward + +1,mokeyDispatch +```js +let next = store.dispatch + +store.dispatch = function dispatchAndLog(action){ + console.log('dispatch',action) + let result = next(action) + console.log('next state',state.getState()) + return result +} + +``` + +2,logger / middleware的代表 的演进 +```js +const patchStoreToAddLogging = (store) => { + const next = store.dispatch + + store.dispatch = (action) => { + console.log('dispatching', action) + let result = next(action) + console.log('next state', store.getState()) + return result + } +} + +patchStoreToAddLogging(store) // ===> 包装 dispatch + +store.dispatch({ + type: 'ADD', + payload +}) + +const loggerV2 = (store) => { + const next = store.dispatch + + return (action) => { + console.log('dispatching', action) + let result = next(action) + console.log('next state', store.getState()) + return result + } +} + +loggerV2(store)({ + type: 'ADD', + payload +}) + +// 注:用法都一样,需执行两步函数,且第二次入参一个 action 对象 + +const applyMiddlewareByMonkeypatching = (store, middlewares) => { + middlewares = middlewares.slice() + middlewares.reverse() + + // 每个中间件都包装了它们的 dispatch + middlewares.foreach(middleware => + store.dispatch = middleware(store) + ) +} +// 注:logger 相当于中间件,logger第一次入参为 store,返回为 dispatch 的拓展函数 + +// 第二版的 logger 获取 dispatch 是通过 store的实例 获取,可以作为参数接收 +// V3 +const loggerV3 = store => next => action => { + console.log('dispating', action) + let result = next(action) + console.log('next state', store.getState()) + return result +} +// loggerV3(store)(dispatch)({type:''}) + +// 对应在 applyware 中, +const applyware = (store, middlewares) => { + middlewares = middlewares.slice() + middlewares.reverse() + + let dispatch = store.dispatch + + middlewares.foreach(middleware => + // 一个疑问:这里会不断覆盖,那之前的不是没啥用 + dispatch = middleware(store)(dispatch) + ) + + return Object.assign({}, store, { dispatch }) +} +// 一个疑问, + +/* + +此处模拟的 applyware 与 Redux 中的 applyMiddleware 的三个重要区别,Redux 中的 applyMiddleware +1,只暴露一个 store API 的子集给 middleware:dispatch(action) 和 getState() +ps:不像手写的 applyware,这里是传入整个 store + +2,当确定你在 middleware 中调用的是 store.dispatch(action)而不是 next(action)时, +则会再次遍历包含当前middleware在内的整个middleware链。 +这对于异步的middleware非常有用 + +3,为了保证只能应用 middleware 一次,它作用在 createStore() 上而不是 store上, +即签名不是 (store,middlewares) => store,而是 (...middlewares) => (createStore) => createStore +ps:没有理解 + +*/ + +// 最终应用在 store 中 +// 任何发送到 store 的 action 都会经过 logger 和 middleware +import {createStore, combineReducers,applyMiddleware} from 'redux' +let todoApp = combineReducers(reducers) +let store = createStore( + todoApp, + // applyMiddleware() 告诉 createStore() 如何处理中间件 + // 上面的 applyMiddleware 是有传入 store,应该是在 createStore 中传入的 + applyMiddleware(loggerV3, crashReporter) +) + +``` + + +## 3.4 搭配 react-router + +1,整个的结构为: +```jsx + + + + +const Root = () => { + +
+ + +
+
+} + +const App = () => { + // 自带 路由相关的属性, + // history, match, location ... +} + +export default withRouter(App) +``` + + +2,新颖的写法 + +```jsx + +const List = () => { + // ... + return ( +
+ {items.map(renderItem)} +
+ ) +} + +``` + +3,real-world,结合 redux-thunk 的流程 + +请求的流程: + +1,自己的流程, + +dispatch({ + type:'request', + +}) + +// + +reducer 是纯函数,无法处理含副作用的作用, + switch(type):{ + case:'request', + + } + + // 此时需要 中间件 + + 而 action 可以处理 副作用, + 示例: + ```js + export const getAllProducts = (dispatch,timeout) => { + // shop.getAllProducts(dispatch(receiveProducts(products))) + // shop.getAllProducts((products) => dispatch(receiveProducts(products))) + shop.getAllProducts(products => { + dispatch(receiveProducts(products)) + },timeout) +} + ``` + + 或者在获得结果/ dispatch + +1,在回调中处理 dispatch + +2, + +action 创建函数返回 action 对象,dispatch(actionCreate()) + +action 对象返回函数, + +// 这里的中间件,完成了三个 dispatch, + +// 封装了 一个执行 dispatch(action) 的函数 +中间件的本质还是 执行 dispatch,