Redux
Store
创建
Redux 是一个状态管理框架,可以与包括 React 在内的许多不同的 Web 技术一起使用。
在 Redux 中,有一个状态对象负责应用程序的整个状态, 这意味着如果有一个包含十个组件且每个组件都有自己的本地状态的 React 项目,那么这个项目的整个状态将通过 Redux store
被定义为单个状态对象, 这是学习 Redux 时要理解的第一个重要原则:Redux store 是应用程序状态的唯一真实来源。
这也意味着,如果应用程序想要更新状态,只能通过 Redux store 执行, 单向数据流可以更轻松地对应用程序中的状态进行监测管理。
Redux store
是一个保存和管理应用程序状态的state
, 可以使用 Redux 对象中的 createStore()
来创建一个 redux store
, 此方法将 reducer
函数作为必需参数, 它只需将 state
作为参数并返回一个 state
即可。
const reducer = (state = 5) => {
return state;
}
let store = Redux.createStore(reducer);
获取状态
Redux store 对象提供了几种与之交互的方法, 比如,可以使用 getState()
方法检索 Redux store 对象中保存的当前 state
。
const store = Redux.createStore(
(state = 5) => state
);
let currentState = store.getState();
Store 监听器
在 Redux store
对象*问数据的另一种方法是 store.subscribe()
。 这允许将监听器函数订阅到 store,只要 action 被 dispatch 就会调用它们。 这个方法的一个简单用途是为 store 订阅一个函数,它只是在每次收到一个 action 并且更新 store 时记录一条消息。
const ADD = 'ADD';
const reducer = (state = 0, action) => {
switch(action.type) {
case ADD:
return state + 1;
default:
return state;
}
};
const store = Redux.createStore(reducer);
let count = 0;
const addOne = () => (count += 1);
store.subscribe(addOne);
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
Action
由于 Redux 是一个状态管理框架,因此更新状态是其核心任务之一。 在 Redux 中,所有状态更新都由 dispatch action 触发, action 只是一个 JavaScript 对象,其中包含有关已发生的 action 事件的信息。 Redux store 接收这些 action 对象,然后更新相应的状态。 有时,Redux action 也会携带一些数据。 例如,在用户登录后携带用户名, 虽然数据是可选的,但 action 必须带有 type
属性,该属性表示此 action 的类型。
可以将 Redux action 视为信使,将有关应用程序中发生的事件信息提供给 Redux store, 然后 store 根据发生的 action 进行状态的更新。
let action = {
type: 'LOGIN'
}
Action Creator
创建 action 后要将 action 发送到 Redux store,以便它可以更新其状态。 在 Redux 中,可以定义动作创建器来完成此任务, action creator 只是一个返回动作的 JavaScript 函数。 换句话说,action creator 创建表示动作事件的对象。
function actionCreator() {
return action;
}
Action Event
dispatch
方法用于将 action 分派给 Redux store, 调用 store.dispatch()
将从 action creator 返回的值发送回 store。
下面的行是等效的,两者都会调度类 LOGIN
类型的 action:
store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });
const store = Redux.createStore(
(state = {login: false}) => state
);
const loginAction = () => {
return {
type: 'LOGIN'
}
};
store.dispatch(loginAction());
Store 里处理 Action (Reducer)
在一个 action 被创建并 dispatch 之后,Redux store 需要知道如何响应该操作。 这就是 reducer
函数存在的意义。 Redux 中的 Reducers 负责响应 action 然后进行状态的修改。 reducer
将 state
和 action
作为参数,并且它总是返回一个新的 state
。 要知道这是 reducer 的唯一的作用。 它不应有任何其他的作用:比如它不应调用 API 接口,也不应存在任何潜在的副作用。 reducer 只是一个接受状态和动作,然后返回新状态的纯函数。
Redux 的另一个关键原则是 state
是只读的。 换句话说,reducer
函数必须始终返回一个新的 state
,并且永远不会直接修改状态。 Redux 不强制改变状态,但是需要在 reducer 函数的代码中强制执行它。
请注意,当前 state
和 dispatch 的 action
将被传递给 reducer,因此可以使用 action.type
直接获取 action 的类型。
const defaultState = {
login: false
};
const reducer = (state = defaultState, action) => {
if (action.type === 'LOGIN') return ({
login: true
});
else return state;
};
const store = Redux.createStore(reducer);
const loginAction = () => {
return {
type: 'LOGIN'
}
};
Switch 处理 Action
可以定义 Redux store 处理多种 action 类型。 假设在 Redux store 中管理用户身份验证。 希望用状态表示用户登录和注销。 使用 state 的 authenticated
属性表示它。 还需要使用 action creators 创建与用户登录和用户注销相对应的 action,以及 action 对象本身。
可以在 reducer
里通过使用 JavaScript 的 switch
来响应不同的 action 事件。 这是编写 Redux reducers 的标准模式。 switch 语句应该切换 action.type
并返回适当的身份验证状态。
另外,不要忘记在 switch 语句中写一个 default
case,返回当前的 state
。 这是很重要的,因为当程序有多个 reducer,当每一个 action 被 dispatch 时它们都会运行,即使 action 与该 reducer 无关。 在这种情况下,要确保返回当前的 state
const defaultState = {
authenticated: false
};
const authReducer = (state = defaultState, action) => {
switch(action.type) {
case "LOGIN": return {authenticated: true};
case "LOGOUT": return {authenticated: false}
default: return state;
}
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
return {
type: 'LOGIN'
}
};
const logoutUser = () => {
return {
type: 'LOGOUT'
}
};
Action Types
在使用 Redux 时的一个常见做法是将操作类型指定为只读,然后在任何使用它们的地方引用这些常量。 可以通过将 action types 使用 const
声明重构正在使用的代码。
const LOGIN = "LOGIN";
const LOGOUT = "LOGOUT";
const defaultState = {
authenticated: false
};
const authReducer = (state = defaultState, action) => {
switch (action.type) {
case LOGIN:
return {
authenticated: true
}
case LOGOUT:
return {
authenticated: false
}
default:
return state;
}
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
return {
type: 'LOGIN'
}
};
const logoutUser = () => {
return {
type: 'LOGOUT'
}
};
注意: 通常以全部大写形式写出常量,这也是 Redux 的标准做法。
发送 Action Data 给 Store
到目前为止,这些 action 并未包含除 type
之外的任何信息。 还可以和把 action 和特定数据一起发送。 事实上,这是非常常见的,因为 action 通常源于一些用户交互,并且往往会携带一些数据, Redux store 经常需要知道这些数据。
const ADD_NOTE = 'ADD_NOTE';
const notesReducer = (state = 'Initial State', action) => {
switch(action.type) {
case ADD_NOTE: return action.text;
default:
return state;
}
};
const addNoteText = (note) => {
return {
type: ADD_NOTE,
text: note
}
};
const store = Redux.createStore(notesReducer);
console.log(store.getState());
store.dispatch(addNoteText('Hello!'));
console.log(store.getState());
组合多个 Reducers
当应用程序的状态开始变得越来越复杂时,可能会将 state 分成多个块。 相反,请记住 Redux 的第一个原则:所有应用程序状态都保存在 store 中的一个简单的 state 对象中。 因此,Redux 提供 reducer 组合作为复杂状态模型的解决方案。 定义多个 reducer 来处理应用程序状态的不同部分,然后将这些 reducer 组合成一个 root reducer。 然后将 root reducer 传递给 Redux createStore()
方法。
为了将多个 reducer 组合在一起,Redux 提供了combineReducers()
方法。 该方法接受一个对象作为参数,在该参数中定义一个属性,该属性将键与特定 reducer 函数关联。 Redux 将使用给定的键值作为关联状态的名称。
通常情况下,当它们在某种程度上是独一无二的,为每个应用程序的 state 创建一个 reducer 是一个很好的做法。 例如,在一个带有用户身份验证的记笔记应用程序中,一个 reducer 可以处理身份验证而另一个处理用户提交的文本和注释。 对于这样的应用程序,可能会编写 combineReducers()
方法,如下所示:
const rootReducer = Redux.combineReducers({
auth: authenticationReducer,
notes: notesReducer
});
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const counterReducer = (state = 0, action) => {
switch(action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
const LOGIN = 'LOGIN';
const LOGOUT = 'LOGOUT';
const authReducer = (state = {authenticated: false}, action) => {
switch(action.type) {
case LOGIN:
return {
authenticated: true
}
case LOGOUT:
return {
authenticated: false
}
default:
return state;
}
};
const rootReducer = Redux.combineReducers({
auth: authReducer,
count: counterReducer
});
const store = Redux.createStore(rootReducer);
中间件 Middleware
目前为止的挑战都在避免讨论异步操作,但它们是 Web 开发中不可避免的一部分。 在某些时候,需要在 Redux 应用程序中使用异步请求,那么如何处理这些类型的请求? Redux 中间件专为此目的而设计,称为 Redux Thunk 中间件。 这里简要介绍如何在 Redux 中使用它。
如果要使用 Redux Thunk 中间件,请将其作为参数传递给 Redux.applyMiddleware()
。 然后将此函数作为第二个可选参数提供给 createStore()
函数, 看一下编辑器底部的代码。 然后,要创建一个异步的 action,需要在 action creator 中返回一个以 dispatch
为参数的函数。 在这个函数中,可以 dispatch action 并执行异步请求。
在此示例中,使用 setTimeout()
模拟异步请求。 通常在执行异步行为之前 dispatch action,以便应用程序状态知道正在请求某些数据(例如,这个状态可以显示加载图标)。 然后,一旦收到数据,就会发送另一个 action,该 action 的 data 是请求返回的数据同时也代表 API 操作完成。
请记住,正在将 dispatch
作为参数传递给这个特殊的 action creator。 需要 dispatch action 时只需将 action 直接传递给 dispatch,中间件就可以处理其余的部分。
const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'
const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }
const handleAsync = () => {
return function(dispatch) {
dispatch(requestingData());
setTimeout(function() {
let data = {
users: ["Jeff", "William", "Alice"]
};
dispatch(receivedData(data));
}, 2500);
}
};
const defaultState = {
fetching: false,
users: []
};
const asyncDataReducer = (state = defaultState, action) => {
switch(action.type) {
case REQUESTING_DATA:
return {
fetching: true,
users: []
}
case RECEIVED_DATA:
return {
fetching: false,
users: action.users
}
default:
return state;
}
};
const store = Redux.createStore(
asyncDataReducer,
Redux.applyMiddleware(ReduxThunk.default)
);
计数器
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
const incAction = () => {
return {
type: INCREMENT
};
};
const decAction = () => {
return {
type: DECREMENT
};
};
const store = Redux.createStore(counterReducer);
永不改变状态 Never Mutate State
最后的几个例子描述了在 Redux 中强制执行状态不变性关键原则的几种方法。 不可变状态意味着永远不直接修改状态,而是返回一个新的状态副本。
如果拍摄 Redux 应用程序一段时间状态的快照,会看到类似 state 1
,state 2
,state 3
,state 4
,...
等等,每个状态可能与最后一个状态相似,但每个状态都是一个独特的数据。 事实上,这种不变性提供了时间旅行调试等功能。
Redux 并没有主动地在其 store 或者 reducer 中强制执行状态不变性,责任落在程序员身上。 幸运的是,JavaScript(尤其是 ES6)提供了一些有用的工具,可以用来强制执行状态的不变性,无论是 string
,number
,array
或 object
。 请注意,字符串和数字是原始值,并且本质上是不可变的。 换句话说,3 总是 3, 不能改变数字 3 的值。 然而,array
或 object
是可变的。 实际上,状态可能会包括 array
或 object
,因为它们经常用来描述一些代表信息的数据结构。
const ADD_TO_DO = 'ADD_TO_DO';
const todos = [
'Go to the store',
'Clean the house',
'Cook dinner',
'Learn to code',
];
const immutableReducer = (state = todos, action) => {
switch(action.type) {
case ADD_TO_DO:
return state.concat(action.todo);
return
default:
return state;
}
};
const addToDo = (todo) => {
return {
type: ADD_TO_DO,
todo
}
}
const store = Redux.createStore(immutableReducer);
数组中使用扩展运算符
ES6 中有助于在 Redux 中强制执行状态不变性的一个解决方案是扩展运算符:...
。 扩展运算符具有很多的应用,其中一种非常适合通过一个已有的数组生成一个新数组。 这是相对较新的但常用的语法。
const immutableReducer = (state = ['Do not mutate state!'], action) => {
switch(action.type) {
case 'ADD_TO_DO': return [...state, action.todo]
default:
return state;
}
};
const addToDo = (todo) => {
return {
type: 'ADD_TO_DO',
todo
}
}
const store = Redux.createStore(immutableReducer);
从数组中删除项目
const immutableReducer = (state = [0,1,2,3,4,5], action) => {
switch(action.type) {
case 'REMOVE_ITEM':
return [
...state.slice(0, action.index),
...state.slice(action.index + 1, state.length)
];
default:
return state;
}
};
const removeItem = (index) => {
return {
type: 'REMOVE_ITEM',
index
}
}
const store = Redux.createStore(immutableReducer);
Object.assign()
最后几个挑战适用于数组,但是当状态是 object
时,有一些方法可以实现状态不变性。 处理对象的一个常用的方法是 Object.assign()
。 Object.assign()
获取目标对象和源对象,并将源对象中的属性映射到目标对象。 任何匹配的属性都会被源对象中的属性覆盖。 通常用于通过传递一个空对象作为第一个参数,然后是要用复制的对象来制作对象的浅表副本。 下面是一个示例:
const newObject = Object.assign({}, obj1, obj2);
const defaultState = {
user: 'CamperBot',
status: 'offline',
friends: '732,982',
community: 'freeCodeCamp'
};
const immutableReducer = (state = defaultState, action) => {
switch(action.type) {
case 'ONLINE':
return Object.assign({}, state, {status: "online"});
default:
return state;
}
};
const wakeUp = () => {
return {
type: 'ONLINE'
}
};
const store = Redux.createStore(immutableReducer);