Redux入门

知识想要不忘,1靠实践2靠记。

1. Promise

Promise是一个承诺。

想象一下,你是一位顶尖歌手,粉丝没日没夜地询问你下首歌什么时候发。

为了从中解放,你承诺(promise)会在单曲发布的第一时间发给他们。你给了粉丝们一个列表。他们可以在上面填写他们的电子邮件地址,以便当歌曲发布后,让所有订阅了的人能够立即收到。即便遇到不测,例如录音室发生了火灾,以致你无法发布新歌,他们也能及时收到相关通知。

这是我们在编程中经常遇到的事儿与真实生活的类比:

  1. “生产者代码(producing code)”会做一些事儿,并且会需要一些时间。例如,通过网络加载数据的代码。它就像一位“歌手”。
  2. “消费者代码(consuming code)”想要在“生产者代码”完成工作的第一时间就能获得其工作成果。许多函数可能都需要这个结果。这些就是“粉丝”。
  3. Promise 是将“生产者代码”和“消费者代码”连接在一起的一个特殊的 JavaScript 对象。用我们的类比来说:这就是就像是“订阅列表”。“生产者代码”花费它所需的任意长度时间来产出所承诺的结果,而 “promise” 将在它(译注:指的是“生产者代码”,也就是下文所说的 executor)准备好时,将结果向所有订阅了的代码开放。

生产者

Promise 对象的构造器(constructor)语法

let promise = new Promise(function(resolve, reject) {
  // executor(生产者代码,“歌手”)
});

executor 会自动运行并尝试执行一项工作。尝试结束后,如果成功则调用 resolve,如果出现 error 则调用 reject。

promise 对象内部属性

  • state —— 最初是 "pending",然后在 resolve 被调用时变为 "fulfilled",或者在 reject 被调用时变为 "rejected"。
  • result —— 最初是 undefined,然后在 resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error。
    image

案例

// 成功实现了的诺言
let promise = new Promise(function(resolve, reject) {
  // 当 promise 被构造完成时,自动执行此函数

  // 1 秒后发出工作已经被完成的信号,并带有结果 "done"
  setTimeout(() => resolve("done"), 1000);
});

通过运行上面的代码,我们可以看到两件事儿:

  1. executor 被自动且立即调用(通过 new Promise)。

  2. executor 接受两个参数:resolve 和 reject。这些函数由 JavaScipt 引擎预先定义,因此我们不需要创建它们。我们只需要在准备好(译注:指的是 executor 准备好)时调用其中之一即可。

经过 1 秒的“处理”后,executor 调用 resolve("done") 来产生结果。这将改变 promise 对象的状态
image

// 失败了的诺言
let promise = new Promise(function(resolve, reject) {
  // 1 秒后发出工作已经被完成的信号,并带有 error
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

image

executor 只能调用一个 resolve 或一个 reject。任何状态的更改都是最终的。
resolve/reject 只需要一个参数(或不包含任何参数),并且将忽略额外的参数
建议以 Error 对象 reject
Resolve/reject 可以立即进行
与最初的 “pending” promise 相反,一个 resolved 或 rejected 的 promise 都会被称为 “settled”。

消费者then,catch,finally

then

promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

第一个参数是一个函数,该函数将在 promise resolved 后运行并接收结果。
第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

// resolve 运行 .then 中的第一个函数
promise.then(
  result => alert(result), // 1 秒后显示 "done!"
  error => alert(error) // 不运行
);

如果我们只对成功完成的情况感兴趣,那么我们可以只为 .then 提供一个函数参数:

catch

如果我们只对 error 感兴趣,那么我们可以使用 null 作为第一个参数:.then(null, errorHandlingFunction)。或者我们也可以使用 .catch(errorHandlingFunction)

finally

.finally(f) 调用与 .then(f, f) 类似,在某种意义上,f 总是在 promise 被 settled 时运行:即 promise 被 resolve 或 reject。

  1. 在 finally 中,我们不知道 promise 是否成功
new Promise((resolve, reject) => {
  setTimeout(() => resolve("result"), 2000)
})
  .finally(() => alert("Promise ready")) // finally 处理程序将结果和 error 传递给下一个处理程序
  .then(result => alert(result)); // <-- .then 对结果进行处理
  1. 我们可以对 settled 的 promise 附加处理程序
    如果 promise 为 pending 状态,.then/catch/finally 处理程序(handler)将等待它。否则,如果 promise 已经是 settled 状态,它们就会运行
// 下面这 promise 在被创建后立即变为 resolved 状态
let promise = new Promise(resolve => resolve("done!"));

promise.then(alert); // done!(现在显示)

这使得 promise 比现实生活中的“订阅列表”方案强大得多。如果歌手已经发布了他们的单曲,然后某个人在订阅列表上进行了注册,则他们很可能不会收到该单曲。实际生活中的订阅必须在活动开始之前进行。

Promise 则更加灵活。我们可以随时添加处理程序(handler):如果结果已经在了,它们就会执行。

promise与回调函数

我们以一个用于加载脚本的 loadScript 函数来对比

// 回调函数
function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`Script load error for ${src}`));

  document.head.append(script);
}
// Promise
function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error(`Script load error for ${src}`));

    document.head.append(script);
  });
}

新函数 loadScript 将不需要回调。取而代之的是,它将创建并返回一个在加载完成时解析(resolve)的 promise 对象。外部代码可以使用 .then 向其添加处理程序(订阅函数)。用法

let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");

promise.then(
  script => alert(`${script.src} is loaded!`),
  error => alert(`Error: ${error.message}`)
);

promise.then(script => alert('Another handler...'));

我们可以看到,使用promise时,我们不需要提前知道成功或者失败后怎么处理,我们只是发布了一套任务。在之后消费者自行使用.then订阅。

Promises Callbacks
Promises 允许我们按照自然顺序进行编码。首先,我们运行 loadScript 和 .then 来处理结果。 在调用 loadScript(script, callback) 时,在我们处理的地方(disposal)必须有一个 callback 函数。换句话说,在调用 loadScript 之前,我们必须知道如何处理结果。
我们可以根据需要,在 promise 上多次调用 .then。每次调用,我们都会在“订阅列表”中添加一个新的“分析”,一个新的订阅函数。 只能有一个回调。

2. Ajax

3. Redux

单向数据流

image

  • 用 state 来描述应用程序在特定时间点的状况
  • 基于 state 来渲染出 View
  • 当发生某些事情(Actions)时(例如用户单击按钮),state 会根据发生的事情进行更新,生成新的 state
  • 基于新的 state 重新渲染 View

Redux思想

当我们有多个组件需要共享和使用相同state时,可能会变得很复杂,尤其是当这些组件位于应用程序的不同部分时。有时这可以通过 "提升 state" 到父组件来解决,但这并不总是有效。
应用中使用集中式的全局状态来管理,并明确更新状态的模式,以便让代码具有可预测性。
image

Action

  • action 是一个具有 type 字段的普通 JavaScript 对象。
  • 用于描述应用程序中发生了什么的事件。
  • action 对象可以有其他字段,其中包含有关发生的事情的附加信息。按照惯例,我们将该信息放在名为 payload 的字段中。
// 典型
const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}

Reducer

  • reducer 是一个函数,接收当前的 state 和一个 action 对象,必要时决定如何更新状态,并返回新状态。
  • 你可以将 reducer 视为一个事件监听器,它根据接收到的 action(事件)类型处理事件。
  • 语法:```(state, action) => newState``
const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // 检查 reducer 是否关心这个 action
  if (action.type === 'counter/increment') {
    // 如果是,复制 `state`
    return {
      ...state,
      // 使用新值更新 state 副本
      value: state.value + 1
    }
  }
  // 返回原来的 state 不变
  return state
}

store

当前 Redux 应用的状态存在于一个名为 store 的对象中。

import { createStore } from 'redux'

const store = createStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}

Dispatch

  • store 有一个方法叫 dispatch。更新 state 的唯一方法是调用 store.dispatch() 并传入一个 action 对象
  • dispatch 一个 action 可以形象的理解为 "触发一个事件"
store.dispatch({ type: 'counter/increment' })

console.log(store.getState())
// {value: 1}

Provider

我们通过在整个 <App> 周围渲染一个<Provider>组件,并将 Redux 存储作为道具传递给 <Provider> 来做告诉hook状态在Redux 存储。在我们这样做一次之后,应用程序中的每个组件都可以在需要时访问 Redux 存储。

connect()

将store和组件联系在一起

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
  • [mapStateToProps(state, [ownProps]): stateProps] (Function): 如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果你省略了这个参数,你的组件将不会监听 Redux store。如果指定了该回调函数中的第二个参数 ownProps,则该参数的值为传递到组件的 props,而且只要组件接收到新的 props,mapStateToProps 也会被调用(例如,当 props 接收到来自父组件一个小小的改动,那么你所使用的 ownProps 参数,mapStateToProps 都会被重新计算)

  • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):

    • 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中dispatch方法会将action creator的返回值作为参数执行。这些属性会被合并到组件的 props 中。
    • 如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起(提示:你也许会用到 Redux 的辅助函数 bindActionCreators())。如果你省略这个 mapDispatchToProps 参数,默认情况下,dispatch 会注入到你的组件 props 中。如果指定了该回调函数中第二个参数 ownProps,该参数的值为传递到组件的 props,而且只要组件接收到新 props,mapDispatchToProps 也会被调用。
  • [mergeProps(stateProps, dispatchProps, ownProps): props] (Function): 如果指定了这个参数,mapStateToProps() 与 mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 action creator 绑定在一起。如果你省略这个参数,默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。

  • [options] (Object) 如果指定这个参数,可以定制 connector 的行为。

    • [pure = true] (Boolean): 如果为 true,connector 将执行 shouldComponentUpdate 并且浅对比 mergeProps 的结果,避免不必要的更新,前提是当前组件是一个“纯”组件,它不依赖于任何的输入或 state 而只依赖于 props 和 Redux store 的 state。默认值为 true。
    • [withRef = false] (Boolean): 如果为 true,connector 会保存一个对被包装组件实例的引用,该引用通过 getWrappedInstance() 方法获得。默认值为 false。

参考
https://zh.javascript.info/promise-basics
https://cn.redux.js.org/tutorials/essentials/part-1-overview-concepts
https://cn.redux.js.org/tutorials/fundamentals/part-5-ui-react#passing-the-store-with-provider
https://www.redux.org.cn/docs/react-redux/api.html

posted @ 2022-03-16 06:05  沧浪浊兮  阅读(149)  评论(0编辑  收藏  举报