Redux入门
知识想要不忘,1靠实践2靠记。
1. Promise
Promise是一个承诺。
想象一下,你是一位顶尖歌手,粉丝没日没夜地询问你下首歌什么时候发。
为了从中解放,你承诺(promise)会在单曲发布的第一时间发给他们。你给了粉丝们一个列表。他们可以在上面填写他们的电子邮件地址,以便当歌曲发布后,让所有订阅了的人能够立即收到。即便遇到不测,例如录音室发生了火灾,以致你无法发布新歌,他们也能及时收到相关通知。
这是我们在编程中经常遇到的事儿与真实生活的类比:
- “生产者代码(producing code)”会做一些事儿,并且会需要一些时间。例如,通过网络加载数据的代码。它就像一位“歌手”。
- “消费者代码(consuming code)”想要在“生产者代码”完成工作的第一时间就能获得其工作成果。许多函数可能都需要这个结果。这些就是“粉丝”。
- 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。
案例
// 成功实现了的诺言
let promise = new Promise(function(resolve, reject) {
// 当 promise 被构造完成时,自动执行此函数
// 1 秒后发出工作已经被完成的信号,并带有结果 "done"
setTimeout(() => resolve("done"), 1000);
});
通过运行上面的代码,我们可以看到两件事儿:
-
executor 被自动且立即调用(通过 new Promise)。
-
executor 接受两个参数:resolve 和 reject。这些函数由 JavaScipt 引擎预先定义,因此我们不需要创建它们。我们只需要在准备好(译注:指的是 executor 准备好)时调用其中之一即可。
经过 1 秒的“处理”后,executor 调用 resolve("done") 来产生结果。这将改变 promise 对象的状态
// 失败了的诺言
let promise = new Promise(function(resolve, reject) {
// 1 秒后发出工作已经被完成的信号,并带有 error
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
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。
- 在 finally 中,我们不知道 promise 是否成功
new Promise((resolve, reject) => { setTimeout(() => resolve("result"), 2000) }) .finally(() => alert("Promise ready")) // finally 处理程序将结果和 error 传递给下一个处理程序 .then(result => alert(result)); // <-- .then 对结果进行处理
- 我们可以对 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
单向数据流
- 用 state 来描述应用程序在特定时间点的状况
- 基于 state 来渲染出 View
- 当发生某些事情(Actions)时(例如用户单击按钮),state 会根据发生的事情进行更新,生成新的 state
- 基于新的 state 重新渲染 View
Redux思想
当我们有多个组件需要共享和使用相同state时,可能会变得很复杂,尤其是当这些组件位于应用程序的不同部分时。有时这可以通过 "提升 state" 到父组件来解决,但这并不总是有效。
应用中使用集中式的全局状态来管理,并明确更新状态的模式,以便让代码具有可预测性。
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
我们通过在整个
周围渲染一个
组件,并将 Redux 存储作为道具传递给
来做告诉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