共计 9049 个字符,预计需要花费 23 分钟才能阅读完成。
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);