createStore()
方法。为了让我们将多个reducer组合在一起,Redux提供了combineReducers()
方法。此方法接受一个对象作为参数,您可以在其中定义将键与特定reducer函数关联的属性。 Redux将使用您为密钥指定的名称作为相关状态的名称。通常,当每个应用程序状态以某种方式不同或唯一时,为每个应用程序状态创建一个减速器是一个好习惯。例如,在具有用户身份验证的笔记记录应用程序中,一个reducer可以处理身份验证,而另一个reducer处理用户正在提交的文本和备注。对于这样的应用程序,我们可能会像这样编写combineReducers()
方法: const rootReducer = Redux.combineReducers({现在,关键
auth:authenticationReducer,
笔记:notesReducer
});
notes
将包含与我们的注释相关联的所有状态,并由notesReducer
处理。这就是如何组合多个reducers来管理更复杂的应用程序状态。在此示例中,Redux存储中保存的状态将是包含auth
和notes
属性的单个对象。 createStore()
方法。
+为了让我们将可以将多个 reducer 组合在一起,Redux 提供了combineReducers()
方法。该方法接受一个对象作为参数,在该参数中定义一个将键与特定 reducer 函数关联的属性。Redux 将使用你给的键值作为关联状态的名称。
+通常情况下,当它们在某种程度上是独一无二的,为每个应用程序的 state 创建一个减少器是一个很好的做法。例如,在一个带有用户身份验证的记笔记应用程序中,一个 reducer 可以处理身份验证而另一个处理用户提交的文本和注释。对于这样的应用程序,我们可能会编写combineReducers()
方法,如下所示:
+
+```js
+const rootReducer = Redux.combineReducers({
+ auth: authenticationReducer,
+ notes: notesReducer
+});
+```
+
+现在,notes
键将包含与我们的注释相关联的所有状态,并由我们的notesReducer
处理。这就是如何组合多个 reducer 来管理更复杂的应用程序状态,在此示例中,Redux store 中保存的状态将是一个包含auth
和notes
属性的简单对象。
+authReducer()
counterReducer()
和authReducer()
函数以及Redux存储。使用Redux.combineReducers()
方法完成rootReducer()
函数的Redux.combineReducers()
。指定counterReducer
一键叫count
和authReducer
一个叫关键auth
。 counterReducer()
和authReducer()
函数以及 Redux store。使用Redux.combineReducers()
方法编写完成rootReducer()
函数。将counterReducer
分配给一个叫做count
的键,将authReducer
分配给一个叫做auth
的键。
+counterReducer
应该递增和递减state
。
+ - text: counterReducer
应该递增和递减state
。
testString: 'assert((function() { const initialState = store.getState().count; store.dispatch({type: INCREMENT}); store.dispatch({type: INCREMENT}); const firstState = store.getState().count; store.dispatch({type: DECREMENT}); const secondState = store.getState().count; return firstState === initialState + 2 && secondState === firstState - 1 })());'
- - text: authReducer
应切换在true
和false
之间进行authenticated
的state
。
+ - text: authenticated
的state
值应该在true
和false
之间切换。
testString: 'assert((function() { store.dispatch({type: LOGIN}); const loggedIn = store.getState().auth.authenticated; store.dispatch({type: LOGOUT}); const loggedOut = store.getState().auth.authenticated; return loggedIn === true && loggedOut === false })());'
- - text: 存储state
应该有两个键: count
,它包含一个数字, auth
,它包含一个对象。 auth
对象应具有authenticated
属性,该属性包含布尔值。
+ - text: store state
应该有两个 key:一个是count
,它包含一个数字。另一个auth
,它包含一个对象。auth
对象应该具有authenticated
的属性,该属性的值应该为布尔值。
testString: "assert((function() { const state = store.getState(); return typeof state.auth === 'object' && typeof state.auth.authenticated === 'boolean' && typeof state.count === 'number' })());"
- - text: 该rootReducer
应该是结合了功能counterReducer
和authReducer
。
+ - text: rootReducer
应该是一个合并了counterReducer
和authReducer
的函数。
testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,''); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })());
```
@@ -69,7 +84,7 @@ const authReducer = (state = {authenticated: false}, action) => {
}
};
-const rootReducer = // define the root reducer here
+const rootReducer = // 在这里定义 root reducer
const store = Redux.createStore(rootReducer);
@@ -84,8 +99,46 @@ const store = Redux.createStore(rootReducer);
## Solution
object
时,有一些方法可以帮助强制执行状态不变性。处理对象的有用工具是Object.assign()
实用程序。 Object.assign()
接受目标对象和源对象,并将源对象中的属性映射到目标对象。任何匹配的属性都会被源对象中的属性覆盖。此行为通常用于通过将空对象作为第一个参数传递,然后传递要复制的对象来生成对象的浅副本。这是一个例子: const newObject = Object.assign({}, obj1, obj2);
这将newObject
创建为一个新object
,其中包含当前存在于obj1
和obj2
中的属性。 object
时,有一些方法可以帮助强制执行状态不变性。处理对象的一个方法是Object.assign()
。 Object.assign()
获取目标对象和源对象,并将源对象中的属性映射到目标对象。任何匹配的属性都会被源对象中的属性覆盖。通常用于通过传递一个空对象作为第一个参数,然后是要用复制的对象来制作对象的浅表副本。这是一个例子:
+const newObject = Object.assign({}, obj1, obj2);
+这会创建newObject
作为新的object
,其中包含obj1
和obj2
中当前存在的属性。
+state
的object
。编辑代码以返回类型为ONLINE
操作的新state
对象,该status
属性将status
属性设置为online
字符串。尝试使用Object.assign()
来完成挑战。 state
的对象
。编辑代码以返回一个新的state
对象,用于类型的 actionONLINE
,它将status
属性设置为字符串online
。尝试使用Object.assign()
来完成挑战。
+defaultState
对象的状态进行初始化。
+ - text: Redux store 应该存在并使用与第 1 行声明的defaultState
对象相同的状态进行初始化。
testString: 'assert((function() { const expectedState = { user: ''CamperBot'', status: ''offline'', friends: ''732,982'', community: ''freeCodeCamp'' }; const initialState = store.getState(); return DeepEqual(expectedState, initialState); })());'
- text: wakeUp
和immutableReducer
都应该是函数。
testString: assert(typeof wakeUp === 'function' && typeof immutableReducer === 'function');
- - text: 调度ONLINE
类型的操作应该将status
中的属性status
更新为online
并且不应该改变状态。
+ - text: dispatch 一个类型为ONLINE
的 action 应该将状态status
更新为online
,并且不应该改变状态。
testString: 'assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch({type: ''ONLINE''}); const finalState = store.getState(); const expectedState = { user: ''CamperBot'', status: ''online'', friends: ''732,982'', community: ''freeCodeCamp'' }; return isFrozen && DeepEqual(finalState, expectedState); })());'
- - text: Object.assign
应该用于返回新状态。
+ - text: Object.assign
应该被用于返回一个新状态。
testString: getUserInput => assert(getUserInput('index').includes('Object.assign'));
```
@@ -47,7 +53,7 @@ const defaultState = {
const immutableReducer = (state = defaultState, action) => {
switch(action.type) {
case 'ONLINE':
- // don't mutate state here or the tests will fail
+ // 不要在这里改变 state 否则测试会失败。
return
default:
return state;
@@ -61,7 +67,6 @@ const wakeUp = () => {
};
const store = Redux.createStore(immutableReducer);
-
```
@@ -73,8 +78,33 @@ const store = Redux.createStore(immutableReducer);
## Solution
store
的单个状态对象定义。这是学习Redux时理解的第一个重要原则:Redux商店是应用程序状态的唯一真实来源。这也意味着,只要您的应用程序的任何部分想要更新状态,它必须通过Redux商店执行此操作。单向数据流可以更轻松地跟踪应用程序中的状态管理。 store
被定义为单个状态对象,这是学习 Redux 时要理解的第一个重要原则:Redux store 是应用程序状态的唯一真实来源。
+这也意味着,如果你的应用程序想要更新状态,只能通过 Redux store 执行,单向数据流可以更轻松地对应用程序中的状态进行监测管理。
+store
是一个保存和管理应用程序state
的对象。 Redux对象上有一个名为createStore()
的方法,您可以使用该方法创建Redux store
。此方法将reducer
函数作为必需参数。 reducer
函数将在稍后的挑战中介绍,并且已在代码编辑器中为您定义。它只是将state
作为参数并返回state
。声明一个store
变量并将其赋值给createStore()
方法,并将reducer
作为参数传入。 注意:编辑器中的代码使用ES6默认参数语法初始化此状态以保存值5
。如果您不熟悉默认参数,可以参考课程中涵盖此主题的ES6部分 。 store
是一个保存和管理应用程序状态的state
,你可以使用 Redux 对象中的createStore()
来创建一个 reduxstore
,此方法将reducer
函数作为必需参数,reducer
函数将在后面的挑战中介绍。该函数已在代码编辑器中为你定义,它只需将state
作为参数并返回一个state
即可。
+声明一个store
变量并把它分配给createStore()
方法,然后把reducer
作为一个参数传入即可。
+注意: 编辑器中的代码使用 ES6 默认参数语法将 state 的值初始化为5
, 如果你不熟悉默认参数,你可以参考 ES6 全部课程,它里面涵盖了这个内容。
+type
属性,该属性指定发生的操作的“类型”。将Redux操作视为信使,将有关应用程序中发生的事件的信息提供给Redux商店。然后,商店根据发生的操作开展更新状态的业务。 type
属性,该属性表示此 action 的类型。
+我们可以将 Redux action 视为信使,将有关应用程序中发生的事件信息提供给 Redux store,然后 store 根据发生的 action 进行状态的更新。
+action
并为其设置一个属性type
设置为字符串'LOGIN'
。 action
并为它设置一个属性type
,并将他的值设置成字符串'LOGIN'
。
+LOGIN
的键属性类型。
+ - text: 该 action 的值应该为LOGIN
。
testString: assert((function() { return action.type === 'LOGIN' })());
```
@@ -33,7 +38,7 @@ tests:
actionCreator()
的函数,该函数在调用时返回action
对象。 actionCreator()
的函数,该函数在调用时返回action
对象。
+actionCreator
应该存在。
testString: assert(typeof actionCreator === 'function');
- - text: 运行actionCreator
函数应该返回操作对象。
+ - text: 运行actionCreator
函数应返回 action 对象。
testString: assert(typeof action === 'object');
- - text: 返回的操作应具有值为LOGIN
的键属性类型。
+ - text: 返回的 action 应具有值为LOGIN
的键值类型。
testString: assert(action.type === 'LOGIN');
```
@@ -38,7 +42,8 @@ tests:
const action = {
type: 'LOGIN'
}
-// Define an action creator here:
+// 在此处定义 action creator
+
```
@@ -51,8 +56,15 @@ const action = {
## Solution
dispatch
方法是您用于将操作分派给Redux存储的方法。调用store.dispatch()
并传递从操作创建者返回的值会将操作发送回商店。回想一下,动作创建者返回一个具有type属性的对象,该属性指定已发生的动作。然后,该方法将操作对象调度到Redux存储。根据之前的挑战示例,以下行是等效的,并且都调度LOGIN
类型的操作: store.dispatch(actionCreator());
store.dispatch({type:'LOGIN'});
dispatch
方法用于将 action 分派给 Redux store,调用store.dispatch()
将从 action creator 返回的值发送回 store。
+action creator 返回一个具有 type 属性的对象,该属性指定已发生的 action,然后,该方法将 action 对象 dispatch 到 Redux store,根据之前的挑战示例,以下内容是等效的,并且都 dispatch 类型为LOGIN
的 action:
+
+```js
+store.dispatch(actionCreator());
+store.dispatch({ type: 'LOGIN' });
+```
+
+false
的login
属性的对象。还有一个名为loginAction()
的动作创建器,它返回LOGIN
类型的动作。通过调用dispatch
方法将LOGIN
操作发送到Redux存储,并传入loginAction()
创建的操作。 loginAction()
的 action creator,它返回类型为LOGIN
的 action,然后通过调用dispatch
方法将LOGIN
的 action dispatch 给 Redux store,并传递loginAction()
创建的 action。
+loginAction
应该返回一个对象,其type
属性设置为字符串LOGIN
。
+ - text: '调用函数loginAction
应该返回一个对象{type:"LOGIN"}。'
testString: assert(loginAction().type === 'LOGIN');
- - text: 应使用属性login
设置为false
的对象初始化存储。
+ - text: 'store 应该初始化一个对象 {login:false}。'
testString: assert(store.getState().login === false);
- - text: store.dispatch()
方法应该用于调度LOGIN
类型的操作。
+ - text: store.dispatch()
方法应该被用于 dispatch 一个类型为LOGIN
的 action。
testString: "getUserInput => assert((function() { let noWhiteSpace = getUserInput('index').replace(/\\s/g,''); return noWhiteSpace.includes('store.dispatch(loginAction())') || noWhiteSpace.includes('store.dispatch({type: \\'LOGIN\\'})') === true })());"
```
@@ -45,7 +56,7 @@ const loginAction = () => {
}
};
-// Dispatch the action here:
+// 在这里 dispatch action
```
@@ -58,8 +69,20 @@ const loginAction = () => {
## Solution
getState()
方法检索Redux存储对象中保存的当前state
。 getState()
方法检索 Redux store 对象中保存的当前的state
。
+store.getState()
从store
检索state
,并将其分配给新变量currentState
。 store
中使用store.getState()
检索state
,并将其分配给新变量currentState
。
+currentState
,并且应该为其分配Redux存储的当前状态。
+ - text: 应该存在一个变量currentState
,并为其分配 Redux store 的当前状态。
testString: getUserInput => assert(currentState === 5 && getUserInput('index').includes('store.getState()'));
```
@@ -37,7 +41,7 @@ const store = Redux.createStore(
(state = 5) => state
);
-// change code below this line
+// 更改此行下方的代码
```
@@ -50,8 +54,14 @@ const store = Redux.createStore(
## Solution
reducer
功能的工作。 Redux中的Reducers负责响应操作而进行的状态修改。 reducer
将state
和action
作为参数,并且它总是返回一个新state
。重要的是要看到这是减速器的唯一作用。它没有任何副作用 - 它从不调用API端点,它从来没有任何隐藏的意外。 reducer只是一个纯函数,它接受状态和动作,然后返回新状态。 Redux的另一个关键原则是state
是只读的。换句话说, reducer
函数必须始终返回一个新的state
副本,而不是直接修改状态。 Redux不强制执行状态不变性,但是,您负责在reducer函数的代码中强制执行它。你将在以后的挑战中练习这一点。 reducer
函数存在的意义。Redux 中的 Reducers 负责响应 action 然后进行状态的修改。reducer
将state
和action
作为参数,并且它总是返回一个新的state
。我们要知道这是 reducer 的唯一的作用。它不应有任何其他的作用:比如它不应调用 API 接口,也不应存在任何潜在的副作用。reducer 只是一个接受状态和动作,然后返回新状态的纯函数。
+Redux 的另一个关键原则是state
是只读的。换句话说,reducer
函数必须始终返回一个新的state
,并且永远不会直接修改状态。Redux 不强制改变状态,但是你需要在你的 reducer 函数的代码中强制执行它,你将在以后的挑战中练习这一点。
+reducer
功能。填写reducer
函数的主体,这样如果它收到'LOGIN'
类型的动作,它将返回一个login
设置为true
的状态对象。否则,它返回当前state
。请注意,当前state
和调度的action
将传递给reducer,因此您可以使用action.type
直接访问操作的类型。 reducer
函数。你需要完善reducer
函数的内容,使得它如果收到类型为'LOGIN'
的action,它将返回一个将login
设置为true
的 state 对象。否则,它就返回当前的state
。请注意,当前state
和dispatch的action
将被传递给reducer,因此你可以使用action.type
直接获取 action 的类型。
+loginAction
应该返回一个对象,其type属性设置为字符串LOGIN
。
+ - text: '调用loginAction
函数应该返回一个对象 {type:"LOGIN"}。'
testString: assert(loginAction().type === 'LOGIN');
- - text: 应使用属性login
设置为false
的对象初始化存储。
+ - text: 'store 应该初始化一个对象 {login:false}。'
testString: assert(store.getState().login === false);
- - text: 调度loginAction
应该将store状态中的login
属性更新为true
。
+ - text: dispatch loginAction
后应将 store 中 state 的login
值更新为true
。
testString: assert((function() { const initialState = store.getState(); store.dispatch(loginAction()); const afterState = store.getState(); return initialState.login === false && afterState.login === true })());
- - text: 如果操作不是LOGIN
类型,则存储应返回当前状态。
+ - text: 如果 action 的类型不是LOGIN
,那么 store 应返回当前的 state。
testString: 'assert((function() { store.dispatch({type: ''__TEST__ACTION__''}); let afterTest = store.getState(); return typeof afterTest === ''object'' && afterTest.hasOwnProperty(''login'') })());'
```
@@ -42,9 +47,9 @@ const defaultState = {
};
const reducer = (state = defaultState, action) => {
- // change code below this line
+ // 修改此行下方的代码
- // change code above this line
+ // 修改此行上方的代码
};
const store = Redux.createStore(reducer);
@@ -54,7 +59,6 @@ const loginAction = () => {
type: 'LOGIN'
}
};
-
```
state 1
, state 2
, state 3
, state 4
, ...
等等,其中每个状态可能与最后一个状态相似,但每个状态是一个独特的数据。事实上,这种不变性提供了您可能听说过的时间旅行调试等功能。 Redux不会在其商店或减少者中主动强制执行状态不变性,而责任落在程序员身上。幸运的是,JavaScript(尤其是ES6)提供了一些有用的工具,可用于强制执行状态的不变性,无论是string
, number
, array
还是object
。请注意,字符串和数字是原始值,并且本质上是不可变的。换句话说,3总是3.您不能更改数字3的值。但是, array
或object
是可变的。实际上,您的状态可能包含array
或object
,因为它们是用于表示许多类型信息的有用数据结构。 state 1
,state 2
,state 3
,state 4
,...
等等,每个状态可能与最后一个状态相似,但每个状态都是一个独特的数据。事实上,这种不变性是什么提供了你可能听说过的时间旅行调试等功能。
+Redux 并没有积极地在其 store 或者 reducer 中强制执行状态不变性,责任落在程序员身上。幸运的是,JavaScript(尤其是 ES6)提供了一些有用的工具,可以用来强制执行状态的不变性,无论是string
,number
,array
或object
。请注意,字符串和数字是原始值,并且本质上是不可变的。换句话说,3 总是 3,你不能改变数字 3 的值。然而,array
或object
是可变的。实际上,你的状态可能包括array
或object
,因为它们在表示许多类型信息的数据结构时非常有用。
+store
和reducer
器,用于管理待办事项。完成在ADD_TO_DO
中写入ADD_TO_DO
情况,以向状态附加新的待办事项。使用标准JavaScript或ES6可以通过几种方法实现此目的。看看是否可以找到一种方法来返回一个新数组,其中action.todo
的项目附加到结尾。 store
和reducer
,用于管理待办事项。完成在 reducer 中编写ADD_TO_DO
的情况,使用标准 JavaScript 或 ES6 可以通过几种方法来实现这一目标。看看是否可以找到一种方法来返回一个新数组,其中来自action.todo
的项目添加到数组的末尾。
+todos
数组的状态进行初始化。
+ - text: Redux store 应该在代码编辑器中存在并使用名字为todos
的数组进行状态初始化。
testString: assert((function() { const todos = [ 'Go to the store', 'Clean the house', 'Cook dinner', 'Learn to code' ]; const initialState = store.getState(); return Array.isArray(initialState) && initialState.join(',') === todos.join(','); })());
- text: addToDo
和immutableReducer
都应该是函数。
testString: assert(typeof addToDo === 'function' && typeof immutableReducer === 'function');
- - text: 在Redux存储上调度ADD_TO_DO
类型的操作应该添加todo
,不应该改变状态。
+ - text: 在 Redux store 上 dispatch 一个类型为ADD_TO_DO
的aciton应该添加一个todo
项,并且不应该改变 state。
testString: assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch(addToDo('__TEST__TO__DO__')); const finalState = store.getState(); const expectedState = [ 'Go to the store', 'Clean the house', 'Cook dinner', 'Learn to code', '__TEST__TO__DO__' ]; return( isFrozen && DeepEqual(finalState, expectedState)); })());
```
@@ -37,6 +43,48 @@ tests:
```jsx
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:
+ // 不要在这里改变 state,否则测试将失败
+ return
+ default:
+ return state;
+ }
+};
+
+// 一个 todo 的例子是 'Learn React',
+const addToDo = (todo) => {
+ return {
+ type: ADD_TO_DO,
+ todo
+ }
+}
+
+const store = Redux.createStore(immutableReducer);
+```
+
+
+
+
+
+store
对象上访问的另一种方法是store.subscribe()
。这允许您将监听器函数订阅到商店,只要针对商店调度操作,就会调用这些函数。此方法的一个简单用途是为您的商店订阅一个功能,只需在每次收到操作并更新商店时记录消息。 store
对象上访问数据的另一种方法是store.subscribe()
。这允许你将监听器函数订阅到 store,只要一个 action 被 dispatch 就会调用它们。这个方法的一个简单用途是为你的 store 订阅一个函数,它只是在每次收到一个 action 并且更新 store 时记录一条消息。
+count
,并将此函数传递给store.subscribe()
方法。您将看到store.dispatch()
连续三次被调用,每次都直接传入一个操作对象。观察操作调度之间的控制台输出以查看更新。 count
,并将此函数传递给store.subscribe()
方法。你将会看到store.dispatch()
连续三次被调用,每次都直接传入一个 action 对象。观察 dispatch action 之间的控制台输出,看看是否发生了更新。
+ADD
操作应该将状态增加1
。
+ - text: 在 store 上 dispatch ADD
action 应该使计数器增加1
。
testString: 'assert((function() { const initialState = store.getState(); store.dispatch({ type: ''ADD'' }); const newState = store.getState(); return newState === (initialState + 1); })());'
- - text: 应该有一个使用store.subscribe
订阅商店的监听器功能。
+ - text: 应该有一个监听函数store.subscribe
订阅 store。
testString: getUserInput => assert(getUserInput('index').includes('store.subscribe('));
- - text: store.subscribe
的回调还应该在存储更新时增加全局count
变量。
+ - text: 在更新 store 时,store.subscribe
应该在回调中使全局变量count
增加。
testString: assert(store.getState() === count);
```
@@ -48,12 +52,12 @@ const reducer = (state = 0, action) => {
const store = Redux.createStore(reducer);
-// global count variable:
+// 用于计数的全局变量:
let count = 0;
-// change code below this line
+// 修改此行下方的代码
-// change code above this line
+// 修改此行上方的代码
store.dispatch({type: ADD});
console.log(count);
@@ -61,7 +65,6 @@ store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
-
```
@@ -71,7 +74,6 @@ console.log(count);
```jsx
count = 0;
-
```
@@ -82,8 +84,34 @@ count = 0;
## Solution
slice()
和concat()
。 slice()
和concat()
。
+[0,1,2,3,4,5]
的状态'
+ - text: Redux store 应该存在并初始化一个[0,1,2,3,4,5]
的状态。
testString: assert((function() { const initialState = store.getState(); return (Array.isArray(initialState) === true && DeepEqual(initialState, [0, 1, 2, 3, 4, 5])); })());
- - text: removeItem
和immutableReducer
都应该是函数。
+ - text: removeItem
和immutableReducer
都应该是一个函数。
testString: assert(typeof removeItem === 'function' && typeof immutableReducer === 'function');
- - text: 调度removeItem
动作创建者应该从状态中删除项目,不应该改变状态。
+ - text: dispatch removeItem
action creator 应该从 state 中删除项目,不应该改变 state。
testString: assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch(removeItem(3)); const state_1 = store.getState(); store.dispatch(removeItem(2)); const state_2 = store.getState(); store.dispatch(removeItem(0)); store.dispatch(removeItem(0)); store.dispatch(removeItem(0)); const state_3 = store.getState(); return isFrozen && DeepEqual(state_1, [0, 1, 2, 4, 5]) && DeepEqual(state_2, [0, 1, 4, 5]) && DeepEqual(state_3, [5]); })());
```
@@ -38,7 +42,7 @@ tests:
const immutableReducer = (state = [0,1,2,3,4,5], action) => {
switch(action.type) {
case 'REMOVE_ITEM':
- // don't mutate state here or the tests will fail
+ // 不要在这里改变 state 否则测试会失败。
return
default:
return state;
@@ -53,7 +57,6 @@ const removeItem = (index) => {
}
const store = Redux.createStore(immutableReducer);
-
```
@@ -65,8 +68,28 @@ const store = Redux.createStore(immutableReducer);
## Solution
type
之外的任何信息。您还可以发送特定数据以及您的操作。事实上,这是非常常见的,因为动作通常源于一些用户交互,并倾向于携带一些数据。 Redux商店经常需要了解这些数据。 type
之外的任何信息。你还可以发送特定数据和 action 一起。事实上,这是非常常见的,因为 action 通常源于一些用户交互,并且往往会携带一些数据,Redux store 经常需要知道这些数据。
+notesReducer()
和一个addNoteText()
动作创建器。完成addNoteText()
函数的主体,以便它返回一个action
对象。该对象应包含值为ADD_NOTE
的type
属性,以及设置为传递给action creator的note
数据的text
属性。当您致电操作创建者时,您将传递可以访问该对象的特定注释信息。接下来,完成在notesReducer()
编写switch
语句。您需要添加一个处理addNoteText()
操作的案例。只要存在类型为ADD_NOTE
的操作,就应该触发此情况,并且应该将传入action
的text
属性作为新state
。该操作将在代码底部发送。完成后,运行代码并观察控制台。这就是将特定于操作的数据发送到商店并在更新存储state
时使用它所需的全部内容。 notesReducer()
和addNoteText()
action creator。完成addNoteText()
函数的主体,这样它就会返回一个action
对象。该对象应该包含一个type
属性,其值为ADD_NOTE
,还有一个text
属性通过 action creator 将值设置为note
。当你调用 action creator 时,你需要传入可以访问该对象的特定注释信息。
+接下来,完成在notesReducer()
中编写的switch
语句。你需要添加一个处理addNoteText()
操作的选项。只要存在ADD_NOTE
类型的 action,就应该触发 case,并且它应该在传入的action
上返回text
属性作为新的state
+这个 action 将在代码底部发送。一旦完成后,运行代码并观察控制台。这就是将特定于 action 的数据发送到 store 并在更新 store state
时使用它所需的全部内容。
+addNoteText
应返回具有键type
和text
的对象。
+ - text: action creator addNoteText
应该返回一个包含type
和text
的对象。
testString: assert((function() { const addNoteFn = addNoteText('__TEST__NOTE'); return addNoteFn.type === ADD_NOTE && addNoteFn.text === '__TEST__NOTE' })());
- - text: 使用addNoteText
操作创建程序调度ADD_NOTE
类型的操作应将state
更新为传递给操作创建者的字符串。
+ - text: dispatch 一个 action creator 是addNoteText
的actionADD_NOTE
,应将state
更新为 action creator 传递的字符串。
testString: assert((function() { const initialState = store.getState(); store.dispatch(addNoteText('__TEST__NOTE')); const newState = store.getState(); return initialState !== newState && newState === '__TEST__NOTE' })());
```
@@ -37,18 +43,18 @@ const ADD_NOTE = 'ADD_NOTE';
const notesReducer = (state = 'Initial State', action) => {
switch(action.type) {
- // change code below this line
+ // 修改此行下方的代码
- // change code above this line
+ // 修改此行上方的代码
default:
return state;
}
};
const addNoteText = (note) => {
- // change code below this line
+ // 修改此行下方的代码
- // change code above this line
+ // 修改此行上方的代码
};
const store = Redux.createStore(notesReducer);
@@ -56,7 +62,6 @@ const store = Redux.createStore(notesReducer);
console.log(store.getState());
store.dispatch(addNoteText('Hello!'));
console.log(store.getState());
-
```
@@ -69,7 +74,33 @@ console.log(store.getState());
authenticated
的属性的单个状态对象来表示它。您还需要动作创建者创建与用户登录和用户注销相对应的操作,以及操作对象本身。 authenticated
属性的单个的 state 对象。你还需要使用 action creators 创建与用户登录和用户注销相对应的 action,以及 action 对象本身。
+reducer
函数以处理多个身份验证操作。在reducer
使用JavaScript switch
语句来响应不同的操作事件。这是编写Redux减速器的标准模式。 switch语句应该切换action.type
并返回适当的身份验证状态。 注意:此时,不要担心状态不变性,因为在这个例子中它很小而且简单。对于每个操作,您可以返回一个新对象 - 例如, {authenticated: true}
。另外,不要忘记在switch语句中写一个返回当前state
的default
大小写。这很重要,因为一旦您的应用程序有多个Reducer,它们都会在执行操作调度时运行,即使操作与该reducer无关。在这种情况下,您需要确保返回当前state
。 reducer
函数来处理多个身份验证操作。可以在reducer
通过使用 JavaScript 的switch
来响应不同的 action 事件。这是编写 Redux reducer 时的标准模式,switch 语句选择action.type
中的一个值并返回相应的身份验证状态。
+注意: 此时,不要担心 state 的不变性,因为在这个示例中它很小而且很简单。所以对于每个操作你都可以返回一个新对象,比如{authenticated:true}
。另外,不要忘记在 switch 语句中写一个default
case,返回当前的state
。这是很重要的,因为一旦你的程序有多个 reducer,当一个 action 被 dispatch 时它们都会运行,即使 action 与该 reducer 无关。在这种情况下,你要确保返回当前的state
+loginUser
应该返回一个对象,其type属性设置为字符串LOGIN
。
+ - text: '调用loginUser
函数应该返回一个 {type:"LOGIN"} 对象。'
testString: assert(loginUser().type === 'LOGIN');
- - text: 调用函数logoutUser
应该返回一个对象,其type属性设置为字符串LOGOUT
。
+ - text: '调用logoutUser
函数应该返回一个 {type:"LOGOUT"} 对象。'
testString: assert(logoutUser().type === 'LOGOUT');
- - text: 应使用经过authenticated
属性设置为false
的对象初始化存储。
+ - text: 'store 应该设置一个初始化对象 {authenticated:false}。'
testString: assert(store.getState().authenticated === false);
- - text: 调度loginUser
应该将store状态中的authenticated
属性更新为true
。
+ - text: dispatch loginUser
应该将 store 中的 state 的authenticated
值更新为true
。
testString: assert((function() { const initialState = store.getState(); store.dispatch(loginUser()); const afterLogin = store.getState(); return initialState.authenticated === false && afterLogin.authenticated === true })());
- - text: 调度logoutUser
应将store状态中的authenticated
属性更新为false
。
+ - text: dispatch logoutUser
应该将 store 中的 state 的authenticated
值更新为false
。
testString: assert((function() { store.dispatch(loginUser()); const loggedIn = store.getState(); store.dispatch(logoutUser()); const afterLogout = store.getState(); return loggedIn.authenticated === true && afterLogout.authenticated === false })());
- - text: authReducer
函数应该使用switch
语句处理多个动作类型。
+ - text: authReducer
函数应该使用switch
语句处理多个 action 类型。
testString: getUserInput => assert( getUserInput('index').toString().includes('switch') && getUserInput('index').toString().includes('case') && getUserInput('index').toString().includes('default'));
```
@@ -46,9 +51,9 @@ const defaultState = {
};
const authReducer = (state = defaultState, action) => {
- // change code below this line
+// 修改此行下方的代码
- // change code above this line
+// 修改此行上方的代码
};
const store = Redux.createStore(authReducer);
@@ -64,7 +69,6 @@ const logoutUser = () => {
type: 'LOGOUT'
}
};
-
```
@@ -76,8 +80,46 @@ const logoutUser = () => {
## Solution
const
声明。 const
声明重构你正在使用的代码。
+LOGIN
和LOGOUT
声明为const
值,并将它们分别分配给字符串'LOGIN'
和'LOGOUT'
。然后,编辑authReducer()
和动作创建者以引用这些常量而不是字符串值。 注意:通常以全部大写形式写常量,这也是Redux中的标准做法。 LOGIN
和LOGOUT
声明为const
类型的值,并为它们分别分配字符串'LOGIN'
和'LOGOUT'
。然后,编辑authReducer()
和 action creators 来引用这些常量而不是字符串值。
+注意: 通常以全部大写形式写出常量,这也是 Redux 的标准做法。
+loginUser
应该返回一个对象,其type
属性设置为字符串LOGIN
。
+ - text: '调用loginUser
函数应该返回一个 {type:"LOGIN"} 对象。'
testString: assert(loginUser().type === 'LOGIN');
- - text: 调用函数logoutUser
应该返回一个对象,其type
属性设置为字符串LOGOUT
。
+ - text: '调用logoutUser
函数应该返回一个 {type:\"LOGOUT\"} 对象。'
testString: assert(logoutUser().type === 'LOGOUT');
- - text: 应使用属性login
设置为false
的对象初始化存储。
+ - text: store 应该初始化一个对象 {login:false}。
testString: assert(store.getState().authenticated === false);
- - text: 调度loginUser
应该将store状态中的login
属性更新为true
。
+ - text: dispatch loginUser
以后应将 store 中的 state 的login
值更新为true
。
testString: assert((function() { const initialState = store.getState(); store.dispatch(loginUser()); const afterLogin = store.getState(); return initialState.authenticated === false && afterLogin.authenticated === true })());
- - text: 调度logoutUser
应将store状态中的login
属性更新为false
。
+ - text: dispatch logoutUser
应将 store 中的 state 的login
值更新为false
。
testString: assert((function() { store.dispatch(loginUser()); const loggedIn = store.getState(); store.dispatch(logoutUser()); const afterLogout = store.getState(); return loggedIn.authenticated === true && afterLogout.authenticated === false })());
- - text: authReducer
函数应该使用switch语句处理多个动作类型。
+ - text: authReducer
函数应该使用 switch 语句处理多个 action 类型。
testString: getUserInput => assert((function() { return typeof authReducer === 'function' && getUserInput('index').toString().includes('switch') && getUserInput('index').toString().includes('case') && getUserInput('index').toString().includes('default') })());
- - text: LOGIN
和LOGOUT
应声明为const
值,并应分配LOGIN
和LOGOUT
字符串。
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/\s/g,''); return (noWhiteSpace.includes('constLOGIN=\'LOGIN\'') || noWhiteSpace.includes('constLOGIN="LOGIN"')) && (noWhiteSpace.includes('constLOGOUT=\'LOGOUT\'') || noWhiteSpace.includes('constLOGOUT="LOGOUT"')) })());
- - text: 动作创建者和减速器应该引用LOGIN
和LOGOUT
常量。
+ - text: '应该使用const LOGIN="LOGIN"
和const LOGOUT="LOGOUT"
的方式声明LOGIN
和LOGOUT
。'
+ testString: const noWhiteSpace = code.replace(/\s/g, ''); assert(/constLOGIN=(['"`])LOGIN\1/.test(noWhiteSpace) && /constLOGOUT=(['"`])LOGOUT\1/.test(noWhiteSpace));
+ - text: action creator 和 reducer 中应该引用LOGIN
和LOGOUT
常量。
testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/\s/g,''); return noWhiteSpace.includes('caseLOGIN:') && noWhiteSpace.includes('caseLOGOUT:') && noWhiteSpace.includes('type:LOGIN') && noWhiteSpace.includes('type:LOGOUT') })());
```
@@ -45,9 +50,9 @@ tests:
Redux.applyMiddleware()
。然后,此语句作为createStore()
函数的第二个可选参数提供。看一下编辑器底部的代码,看看这个。然后,要创建异步操作,您将在动作创建器中返回一个函数,该函数将dispatch
作为参数。在此函数中,您可以分派操作并执行异步请求。在此示例中,使用setTimeout()
调用模拟异步请求。在启动任何异步行为之前调度操作是很常见的,这样您的应用程序状态就知道正在请求某些数据(例如,此状态可能会显示加载图标)。然后,一旦收到数据,就会发送另一个操作,该操作将数据作为有效负载以及操作完成的信息。请记住,您将dispatch
作为参数传递给此特殊操作创建者。这是您用于分派操作的方法,您只需将操作直接传递给调度,中间件就可以处理其余操作。 Redux.applyMiddleware()
。然后将此函数作为第二个可选参数提供给createStore()
函数,看一下编辑器底部的代码,然后,要创建一个异步的 action,你需要在 action creator 中返回一个以dispatch
为参数的函数。在这个函数中,你可以 dispatch action 并执行异步请求。
+在此示例中,使用setTimeout()
调用模拟异步请求。通常在执行异步行为之前 dispatch action,以便应用程序状态知道正在请求某些数据(例如,这个状态可以显示加载图标)。然后,一旦收到数据,就会发送另一个 action,这个 action 完成的时间将作为数据的一个有效值。
+请记住,你正在通过将dispatch
作为参数传递给这个特殊的 action creator。你用于 dispatch 你的 action 时只需将 action 直接传递给 dispatch,中间件就可以处理其余的部分。
+handleAsync()
动作创建器中写入两个调度。调度requestingData()
在之前setTimeout()
模拟API调用)。然后,在收到(假装)数据后,调度receivedData()
操作,传入此数据。现在您知道如何在Redux中处理异步操作。其他一切继续像以前一样。 handleAsync()
的 action creator 中编写两个 dispatch 事件。在setTimeout()
(模拟 API 调用)之前 dispatchrequestingData()
。然后在收到(模拟)数据后,dispatchreceivedData()
action,传入这些数据。现在你知道如何处理 Redux 中的异步操作,其他所有操作都继续像以前一样。
+requestingData
操作创建者应返回类型等于REQUESTING_DATA
值的对象。
+ - text: requesData
action creator 应返回类型和值都为REQUESTING_DATA
的对象。
testString: assert(requestingData().type === REQUESTING_DATA);
- - text: receivedData
操作创建者应返回类型等于RECEIVED_DATA
值的对象。
+ - text: receivedData
action creator 应返回类型和值都等于RECEIVED_DATA
的对象。
testString: assert(receivedData('data').type === RECEIVED_DATA);
- - text: asyncDataReducer
应该是一个函数。
+ - text: asyncDataReducer
必须是个函数。
testString: assert(typeof asyncDataReducer === 'function');
- - text: 调度requestedData操作创建者应该将获取的store state
属性更新为true
。
+ - text: 分发 requestedData 后 action creator 应该将 store 中的state
的 fetching 的值更新为 true
。
testString: assert((function() { const initialState = store.getState(); store.dispatch(requestingData()); const reqState = store.getState(); return initialState.fetching === false && reqState.fetching === true })());
- - text: 调度handleAsync
应调度数据请求操作,然后在延迟后调度接收的数据操作。
+ - text: 如果要 dispatch handleAsync
应先 dispatch 数据请求的 action,然后在收到请求结果后再 dispatch 接收的数据 action。
testString: assert((function() { const noWhiteSpace = handleAsync.toString().replace(/\s/g,''); return noWhiteSpace.includes('dispatch(requestingData())') === true && noWhiteSpace.includes('dispatch(receivedData(data))') === true })());
```
@@ -47,13 +54,13 @@ const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users}
const handleAsync = () => {
return function(dispatch) {
- // dispatch request action here
+ // 在这里 dispatch 请求的 action
setTimeout(function() {
let data = {
users: ['Jeff', 'William', 'Alice']
}
- // dispatch received data action here
+ // 在这里 dispatch 接收到的数据 action
}, 2500);
}
@@ -85,7 +92,6 @@ const store = Redux.createStore(
asyncDataReducer,
Redux.applyMiddleware(ReduxThunk.default)
);
-
```
@@ -97,8 +103,52 @@ const store = Redux.createStore(
## Solution
...
扩展运算符具有各种应用程序,其中一个非常适合于从现有阵列生成新阵列的先前挑战。这是相对较新的,但常用的语法。例如,如果你有一个数组myArray
并且写: let newArray = [...myArray];
newArray
现在是myArray
的克隆。两个阵列仍然分别存在于内存中。如果你执行像newArray.push(5)
这样的变异, myArray
不会改变。 ...
有效地将 myArray
的值展开到一个新数组中。要克隆数组但在新数组中添加其他值,可以编写[...myArray, 'new value']
。这将返回一个由myArray
中的值和字符串'new value'
组成的新数组作为最后一个值。扩展语法可以像这样在数组组合中多次使用,但重要的是要注意它只是生成数组的浅表副本。也就是说,它只为一维数组提供不可变的数组操作。 ...
。扩展运算符具有很多的应用,其中一种非常适合通过一个已有的数组生成一个新数组。这是相对较新的,但常用的语法。例如,如果你有一个数组myArray
并写:
+let newArray = [...myArray];
+newArray
现在是myArray
的克隆。两个数组仍然在内存中单独存在。如果你执行像newArray.push(5)
这样的突变,myArray
不会改变。...
有效将myArray
中的值传播到新数组中。要克隆数组但在新数组中添加其他值,可以编写[... myArray,'new value']
。这将返回一个由中的值组成的新数组。
myArray和字符串'new value'
作为最后一个值。扩展语法可以像这样在数组组合中多次使用,但重要的是要注意它只做一个浅拷贝这就是说,它只为一维数组提供了不可变的数组操作。
+[Do not mutate state!]
。'
+ - text: Redux store 应该存在并初始化一个[Do not mutate state!]
的状态。
testString: assert((function() { const initialState = store.getState(); return ( Array.isArray(initialState) === true && initialState[0] === 'Do not mutate state!'); })());
- - text: addToDo
和immutableReducer
都应该是函数。
+ - text: addToDo
和immutableReducer
都应该是一个函数。
testString: assert(typeof addToDo === 'function' && typeof immutableReducer === 'function');
- - text: 在Redux存储上调度ADD_TO_DO
类型的操作应该添加todo
,不应该改变状态。
+ - text: 在 Redux store 上 dispatch 一个类型为ADD_TO_DO
aciton 应该添加一个todo
项,并且不应该改变 state。
testString: assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch(addToDo('__TEST__TO__DO__')); const finalState = store.getState(); const expectedState = [ 'Do not mutate state!', '__TEST__TO__DO__' ]; return( isFrozen && DeepEqual(finalState, expectedState)); })());
- - text: 应使用扩展运算符返回新状态。
+ - text: 应使用扩展运算符返回新的 state。
testString: getUserInput => assert(getUserInput('index').includes('...state'));
```
@@ -40,7 +46,7 @@ tests:
const immutableReducer = (state = ['Do not mutate state!'], action) => {
switch(action.type) {
case 'ADD_TO_DO':
- // don't mutate state here or the tests will fail
+ // 不要在这里改变 state 否则测试会失败。
return
default:
return state;
@@ -55,7 +61,6 @@ const addToDo = (todo) => {
}
const store = Redux.createStore(immutableReducer);
-
```
@@ -67,8 +72,28 @@ const store = Redux.createStore(immutableReducer);
## Solution
state
不变性的一些细节,但首先,这里是对迄今为止所学到的所有内容的回顾。 state
不变性的一些细节,但是,这里是对你到目前为止学到的所有内容的回顾。
+incAction
和decAction
操作创建者, decAction
counterReducer()
, INCREMENT
和DECREMENT
操作类型,最后定义Redux store
。一旦完成,您应该能够发送INCREMENT
或DECREMENT
操作来增加或减少store
保存的状态。祝你好运第一个Redux应用程序! incAction
和decActio
action creator counterReducer()
,INCREMENT
和DECREMENT
action 类型,最后是 Redux store
。一旦完成,你应该能够 dispatch INCREMENT
或DECREMENT
动作来增加或减少store
中保存的状态。开始构建你的第一个 Redux 应用程序吧,祝你好运!
+incAction
应返回type
等于INCREMENT
值的动作对象
+ - text: 'action creator incAction
应返回一个 action 对象 {type:"INCREMENT"}。'
testString: assert(incAction().type ===INCREMENT);
- - text: 动作创建者decAction
应与返回动作对象type
等于的值DECREMENT
+ - text: 'action creator decAction
应返回一个 action 对象 {type:"DECREMENT"}。'
testString: assert(decAction().type === DECREMENT);
- - text: Redux存储应该以0 state
初始化。
+ - text: Redux store 应该将state
初始化为 0。
testString: assert(store.getState() === 0);
- - text: 在Redux存储上调度incAction
应该将state
增加1。
+ - text: 在 Redux store 上 dispatch incAction
应该将state
增加 1。
testString: assert((function() { const initialState = store.getState(); store.dispatch(incAction()); const incState = store.getState(); return initialState + 1 === incState })());
- - text: 在Redux存储上调度decAction
应该将state
减1。
+ - text: 在 Redux store 上 dispatch decAction
应该将state
减少 1。
testString: assert((function() { const initialState = store.getState(); store.dispatch(decAction()); const decState = store.getState(); return initialState - 1 === decState })());
- - text: counterReducer
应该是一个函数
+ - text: counterReducer
必须是一个函数。
testString: assert(typeof counterReducer === 'function');
```
@@ -41,17 +45,16 @@ tests: