feat: add 'back/front end' in curriculum (#42596)
* chore: rename APIs and Microservices to include "Backend" (#42515) * fix typo * fix typo * undo change * Corrected grammar mistake Corrected a grammar mistake by removing a comma. * change APIs and Microservices cert title * update title * Change APIs and Microservices certi title * Update translations.json * update title * feat(curriculum): rename apis and microservices cert * rename folder structure * rename certificate * rename learn Markdown * apis-and-microservices -> back-end-development-and-apis * update backend meta * update i18n langs and cypress test Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * fix: add development to front-end libraries (#42512) * fix: added-the-word-Development-to-front-end-libraries * fix/added-the-word-Development-to-front-end-libraries * fix/added-word-development-to-front-end-libraries-in-other-related-files * fix/added-the-word-Development-to-front-end-and-all-related-files * fix/removed-typos-from-last-commit-in-index.md * fix/reverted-changes-that-i-made-to-dependecies * fix/removed xvfg * fix/reverted changes that i made to package.json * remove unwanted changes * front-end-development-libraries changes * rename backend certSlug and README * update i18n folder names and keys * test: add legacy path redirect tests This uses serve.json from the client-config repo, since we currently use that in production * fix: create public dir before moving serve.json * fix: add missing script * refactor: collect redirect tests * test: convert to cy.location for stricter tests * rename certificate folder to 00-certificates * change crowdin config to recognise new certificates location * allow translations to be used Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com> * add forwards slashes to path redirects * fix cypress path tests again * plese cypress * fix: test different challenge Okay so I literally have no idea why this one particular challenge fails in Cypress Firefox ONLY. Tom and I paired and spun a full build instance and confirmed in Firefox the page loads and redirects as expected. Changing to another bootstrap challenge passes Cypress firefox locally. Absolutely boggled by this. AAAAAAAAAAAAAAA * fix: separate the test Okay apparently the test does not work unless we separate it into a different `it` statement. >:( >:( >:( >:( Co-authored-by: Sujal Gupta <55016909+heysujal@users.noreply.github.com> Co-authored-by: Noor Fakhry <65724923+NoorFakhry@users.noreply.github.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>
This commit is contained in:
@ -0,0 +1,175 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036154
|
||||
title: 组合多个 Reducers
|
||||
challengeType: 6
|
||||
forumTopicId: 301436
|
||||
dashedName: combine-multiple-reducers
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
当应用程序的状态开始变得越来越复杂时,可能会将 state 分成多个块。 相反,请记住 Redux 的第一个原则:所有应用程序状态都保存在 store 中的一个简单的 state 对象中。 因此,Redux 提供 reducer 组合作为复杂状态模型的解决方案。 定义多个 reducer 来处理应用程序状态的不同部分,然后将这些 reducer 组合成一个 root reducer。 然后将 root reducer 传递给 Redux `createStore()`方法。
|
||||
|
||||
为了将多个 reducer 组合在一起,Redux 提供了`combineReducers()`方法。 该方法接受一个对象作为参数,在该参数中定义一个属性,该属性将键与特定 reducer 函数关联。 Redux 将使用给定的键值作为关联状态的名称。
|
||||
|
||||
通常情况下,当它们在某种程度上是独一无二的,为每个应用程序的 state 创建一个 reducer 是一个很好的做法。 例如,在一个带有用户身份验证的记笔记应用程序中,一个 reducer 可以处理身份验证而另一个处理用户提交的文本和注释。 对于这样的应用程序,可能会编写 `combineReducers()` 方法,如下所示:
|
||||
|
||||
```js
|
||||
const rootReducer = Redux.combineReducers({
|
||||
auth: authenticationReducer,
|
||||
notes: notesReducer
|
||||
});
|
||||
```
|
||||
|
||||
现在,`notes` 键将包含与注释相关联的所有状态,并由 `notesReducer` 处理。 这就是组合多个 reducer 来管理更复杂的应用程序状态的方式, 在此示例中,Redux store 中保存的状态将是一个包含 `auth` 和 `notes` 属性的简单对象。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代码编辑器中提供了 `counterReducer()` 和 `authReducer()` 函数以及 Redux store。 使用 `Redux.combineReducers()` 方法编写完成 `rootReducer()` 函数。 将 `counterReducer` 分配给一个叫做 `count` 的键,将 `authReducer` 分配给一个叫做 `auth` 的键。
|
||||
|
||||
# --hints--
|
||||
|
||||
`counterReducer` 应该递增和递减 `state`。
|
||||
|
||||
```js
|
||||
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;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`authReducer` 应该可以使 `authenticated` 的 `state` 值在 `true` 和 `false` 之间切换。
|
||||
|
||||
```js
|
||||
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;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
store `state` 应该有两个 key:一个是 `count`,它包含一个数字。 另一个 `auth`,它包含一个对象。 `auth` 对象应该具有 `authenticated` 的属性,该属性的值应该为布尔值。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const state = store.getState();
|
||||
return (
|
||||
typeof state.auth === 'object' &&
|
||||
typeof state.auth.authenticated === 'boolean' &&
|
||||
typeof state.count === 'number'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`rootReducer` 应该是一个合并了 `counterReducer` 和 `authReducer` 的函数。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
const noWhiteSpace = __helpers.removeWhiteSpace(getUserInput('index'));
|
||||
return (
|
||||
typeof rootReducer === 'function' &&
|
||||
noWhiteSpace.includes('Redux.combineReducers')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
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 = // Define the root reducer here
|
||||
|
||||
const store = Redux.createStore(rootReducer);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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({
|
||||
count: counterReducer,
|
||||
auth: authReducer
|
||||
});
|
||||
|
||||
const store = Redux.createStore(rootReducer);
|
||||
```
|
@ -0,0 +1,133 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403615b
|
||||
title: 使用 Object.assign 拷贝对象
|
||||
challengeType: 6
|
||||
forumTopicId: 301437
|
||||
dashedName: copy-an-object-with-object-assign
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
最后几个挑战适用于数组,但是当状态是 `object` 时,有一些方法可以实现状态不变性。 处理对象的一个常用的方法是 `Object.assign()`。 `Object.assign()` 获取目标对象和源对象,并将源对象中的属性映射到目标对象。 任何匹配的属性都会被源对象中的属性覆盖。 通常用于通过传递一个空对象作为第一个参数,然后是要用复制的对象来制作对象的浅表副本。 这是一个例子:
|
||||
|
||||
```js
|
||||
const newObject = Object.assign({}, obj1, obj2);
|
||||
```
|
||||
|
||||
这会创建 `newObject` 作为新的 `object`,其中包含 `obj1` 和 `obj2` 中当前存在的属性。
|
||||
|
||||
# --instructions--
|
||||
|
||||
Redux state 和 actions 被修改为处理 `state` 的 `object` 。 编辑代码,为类型为 `ONLINE` 的 actions 返回一个新的 `state` 对象,这个类型将 `status` 属性设置为 `online` 字符串。 尝试使用 `Object.assign()` 来完成挑战。
|
||||
|
||||
# --hints--
|
||||
|
||||
Redux store 应该存在,并使用与第一行声明的 `defaultState` 对象相同的状态进行初始化。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const expectedState = {
|
||||
user: 'CamperBot',
|
||||
status: 'offline',
|
||||
friends: '732,982',
|
||||
community: 'freeCodeCamp'
|
||||
};
|
||||
const initialState = store.getState();
|
||||
return DeepEqual(expectedState, initialState);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`wakeUp` 和 `immutableReducer` 都应该是函数。
|
||||
|
||||
```js
|
||||
assert(typeof wakeUp === 'function' && typeof immutableReducer === 'function');
|
||||
```
|
||||
|
||||
调用一个类型为 `ONLINE` 的 action,应该将状态中的 `status` 更新为 `online`,并且不应该改变状态。
|
||||
|
||||
```js
|
||||
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);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Object.assign` 应该被用于返回一个新状态。
|
||||
|
||||
```js
|
||||
(getUserInput) => assert(getUserInput('index').includes('Object.assign'));
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const defaultState = {
|
||||
user: 'CamperBot',
|
||||
status: 'offline',
|
||||
friends: '732,982',
|
||||
community: 'freeCodeCamp'
|
||||
};
|
||||
|
||||
const immutableReducer = (state = defaultState, action) => {
|
||||
switch(action.type) {
|
||||
case 'ONLINE':
|
||||
// Don't mutate state here or the tests will fail
|
||||
return
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const wakeUp = () => {
|
||||
return {
|
||||
type: 'ONLINE'
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(immutableReducer);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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);
|
||||
```
|
@ -0,0 +1,61 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403614b
|
||||
title: 创建一个 Redux Store
|
||||
challengeType: 6
|
||||
forumTopicId: 301439
|
||||
dashedName: create-a-redux-store
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Redux 是一个状态管理框架,可以与包括 React 在内的许多不同的 Web 技术一起使用。
|
||||
|
||||
在 Redux 中,有一个状态对象负责应用程序的整个状态, 这意味着如果你有一个包含十个组件且每个组件都有自己的本地状态的 React 项目,那么这个项目的整个状态将通过 Redux `store` 被定义为单个状态对象, 这是学习 Redux 时要理解的第一个重要原则:Redux store 是应用程序状态的唯一真实来源。
|
||||
|
||||
这也意味着,如果应用程序想要更新状态,**只能**通过 Redux store 执行, 单向数据流可以更轻松地对应用程序中的状态进行监测管理。
|
||||
|
||||
# --instructions--
|
||||
|
||||
Redux `store` 是一个保存和管理应用程序状态的`state`, 可以使用 Redux 对象中的 `createStore()` 来创建一个 redux `store`, 此方法将 `reducer` 函数作为必需参数, `reducer` 函数将在后面的挑战中介绍。该函数已在代码编辑器中为你定义, 它只需将 `state` 作为参数并返回一个 `state` 即可。
|
||||
|
||||
声明一个 `store` 变量并把它分配给 `createStore()` 方法,然后把 `reducer` 作为一个参数传入即可。
|
||||
|
||||
**注意**: 编辑器中的代码使用 ES6 默认参数语法将 state 的值初始化为 `5`, 如果你不熟悉默认参数,你可以参考[ES6 全部课程](https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/es6/set-default-parameters-for-your-functions),它里面涵盖了这个内容。
|
||||
|
||||
# --hints--
|
||||
|
||||
Redux store 应当存在。
|
||||
|
||||
```js
|
||||
assert(typeof store.getState === 'function');
|
||||
```
|
||||
|
||||
Redux store 的 state 的值应该为 5。
|
||||
|
||||
```js
|
||||
assert(store.getState() === 5);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const reducer = (state = 5) => {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Redux methods are available from a Redux object
|
||||
// For example: Redux.createStore()
|
||||
// Define the store here:
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
const reducer = (state = 5) => {
|
||||
return state;
|
||||
}
|
||||
|
||||
const store = Redux.createStore(reducer);
|
||||
```
|
@ -0,0 +1,55 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403614d
|
||||
title: 定义一个 Redux Action
|
||||
challengeType: 6
|
||||
forumTopicId: 301440
|
||||
dashedName: define-a-redux-action
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
由于 Redux 是一个状态管理框架,因此更新状态是其核心任务之一。 在 Redux 中,所有状态更新都由 dispatch action 触发, action 只是一个 JavaScript 对象,其中包含有关已发生的 action 事件的信息。 Redux store 接收这些 action 对象,然后更新相应的状态。 有时,Redux action 也会携带一些数据。 例如,在用户登录后携带用户名, 虽然数据是可选的,但 action 必须带有 `type` 属性,该属性表示此 action 的类型。
|
||||
|
||||
我们可以将 Redux action 视为信使,将有关应用程序中发生的事件信息提供给 Redux store, 然后 store 根据发生的 action 进行状态的更新。
|
||||
|
||||
# --instructions--
|
||||
|
||||
编写 Redux action 就像声明具有 type 属性的对象一样简单, 声明一个对象 `action` 并为它设置一个属性 `type`,并将它的值设置成字符串`'LOGIN'`。
|
||||
|
||||
# --hints--
|
||||
|
||||
`action` 对象应该存在。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
return typeof action === 'object';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`action` 对象应该有一个值为 `LOGIN` 的 `type` 属性。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
return action.type === 'LOGIN';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
// Define an action here:
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
const action = {
|
||||
type: 'LOGIN'
|
||||
}
|
||||
```
|
@ -0,0 +1,57 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403614e
|
||||
title: 定义一个 Action Creator
|
||||
challengeType: 6
|
||||
forumTopicId: 301441
|
||||
dashedName: define-an-action-creator
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
创建 action 后要将 action 发送到 Redux store,以便它可以更新其状态。 在 Redux 中,可以定义动作创建器来完成此任务, action creator 只是一个返回动作的 JavaScript 函数, 换句话说,action creator 创建表示动作事件的对象。
|
||||
|
||||
# --instructions--
|
||||
|
||||
定义名为 `actionCreator()` 的函数,该函数在调用时返回 `action` 对象。
|
||||
|
||||
# --hints--
|
||||
|
||||
函数 `actionCreator` 应该存在。
|
||||
|
||||
```js
|
||||
assert(typeof actionCreator === 'function');
|
||||
```
|
||||
|
||||
运行 `actionCreator` 函数应返回 `action` 对象。
|
||||
|
||||
```js
|
||||
assert(typeof action === 'object');
|
||||
```
|
||||
|
||||
返回的 `action` 对象应该有一个值为 `LOGIN` 的 `type` 属性。
|
||||
|
||||
```js
|
||||
assert(action.type === 'LOGIN');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const action = {
|
||||
type: 'LOGIN'
|
||||
}
|
||||
// Define an action creator here:
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
const action = {
|
||||
type: 'LOGIN'
|
||||
}
|
||||
const actionCreator = () => {
|
||||
return action;
|
||||
};
|
||||
```
|
@ -0,0 +1,85 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403614f
|
||||
title: 分发 Action Event
|
||||
challengeType: 6
|
||||
forumTopicId: 301442
|
||||
dashedName: dispatch-an-action-event
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`dispatch` 方法用于将 action 分派给 Redux store, 调用 `store.dispatch()` 将从 action creator 返回的值发送回 store。
|
||||
|
||||
回想一下,动作创建者返回一个具有 type 属性的对象,该属性指定已发生的动作。 然后该方法会将一个 action 对象发送到 Redux store。 基于上一个挑战的示例,下面的行是等效的,两者都会调度类 `LOGIN` 类型的 action:
|
||||
|
||||
```js
|
||||
store.dispatch(actionCreator());
|
||||
store.dispatch({ type: 'LOGIN' });
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
代码编辑器中的 Redux store 具有初始化状过的 state,包含 `login` 属性当前设置为 `false`的对象, 还有一个名为 `loginAction()` 的 action creator,它返回类型为 `LOGIN` 的 action, 然后通过调用 `dispatch` 方法将 `LOGIN` 的 action dispatch 给 Redux store,并传入 `loginAction()` 创建的 action。
|
||||
|
||||
# --hints--
|
||||
|
||||
调用函数 `loginAction` 应该返回一个 `type` 属性设置为字符串 `LOGIN` 的对象。
|
||||
|
||||
```js
|
||||
assert(loginAction().type === 'LOGIN');
|
||||
```
|
||||
|
||||
store 应该用属性 `login` 设置为 `false` 的对象初始化。
|
||||
|
||||
```js
|
||||
assert(store.getState().login === false);
|
||||
```
|
||||
|
||||
`store.dispatch()` 方法应该被用于 dispatch 一个类型为 `LOGIN` 的 action。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
let noWhiteSpace = getUserInput('index').replace(/\s/g, '');
|
||||
return (
|
||||
noWhiteSpace.includes('store.dispatch(loginAction())') ||
|
||||
noWhiteSpace.includes("store.dispatch({type: 'LOGIN'})") === true
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const store = Redux.createStore(
|
||||
(state = {login: false}) => state
|
||||
);
|
||||
|
||||
const loginAction = () => {
|
||||
return {
|
||||
type: 'LOGIN'
|
||||
}
|
||||
};
|
||||
|
||||
// Dispatch the action here:
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
const store = Redux.createStore(
|
||||
(state = {login: false}) => state
|
||||
);
|
||||
|
||||
const loginAction = () => {
|
||||
return {
|
||||
type: 'LOGIN'
|
||||
}
|
||||
};
|
||||
|
||||
store.dispatch(loginAction());
|
||||
```
|
@ -0,0 +1,55 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403614c
|
||||
title: 从 Redux Store 获取状态
|
||||
challengeType: 6
|
||||
forumTopicId: 301443
|
||||
dashedName: get-state-from-the-redux-store
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Redux store 对象提供了几种与之交互的方法, 比如,可以使用 `getState()` 方法检索 Redux store 对象中保存的当前 `state`。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在代码编辑器中可以将上一个挑战中的代码更简洁地重写, 在 `store` 中使用 `store.getState()` 检索`state`,并将其分配给新变量 `currentState`。
|
||||
|
||||
# --hints--
|
||||
|
||||
Redux store 的 state 应该有一个初始值 5。
|
||||
|
||||
```js
|
||||
assert(store.getState() === 5);
|
||||
```
|
||||
|
||||
应该存在一个变量 `currentState`,并为其分配 Redux store 的当前状态。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
currentState === 5 && getUserInput('index').includes('store.getState()')
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const store = Redux.createStore(
|
||||
(state = 5) => state
|
||||
);
|
||||
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
const store = Redux.createStore(
|
||||
(state = 5) => state
|
||||
);
|
||||
|
||||
// Change code below this line
|
||||
const currentState = store.getState();
|
||||
```
|
@ -0,0 +1,108 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036150
|
||||
title: 在 Store 里处理 Action
|
||||
challengeType: 6
|
||||
forumTopicId: 301444
|
||||
dashedName: handle-an-action-in-the-store
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
在一个 action 被创建并 dispatch 之后,Redux store 需要知道如何响应该操作。 这就是 `reducer` 函数存在的意义。 Redux 中的 Reducers 负责响应 action 然后进行状态的修改。 `reducer` 将 `state` 和 `action` 作为参数,并且它总是返回一个新的 `state`。 我们要知道这是 reducer 的**唯一**的作用。 它不应有任何其他的作用:比如它不应调用 API 接口,也不应存在任何潜在的副作用。 reducer 只是一个接受状态和动作,然后返回新状态的纯函数。
|
||||
|
||||
Redux 的另一个关键原则是 `state` 是只读的。 换句话说,`reducer` 函数必须**始终**返回一个新的 `state`,并且永远不会直接修改状态。 Redux 不强制改变状态,但是需要在 reducer 函数的代码中强制执行它, 以后的挑战会练习这一点。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代码编辑器中具有前面的示例以及一个 `reducer` 函数。 需要完善 `reducer` 函数的内容,使得它如果收到类型为`'LOGIN'`的action,它将返回一个将 `login` 设置为 `true` 的 state 对象。 否则,它就返回当前的 `state`。 请注意,当前 `state` 和 dispatch 的 `action` 将被传递给 reducer,因此可以使用 `action.type` 直接获取 action 的类型。
|
||||
|
||||
# --hints--
|
||||
|
||||
调用函数 `loginAction` 应该返回一个 type 属性设置为字符串 `LOGIN` 的对象。
|
||||
|
||||
```js
|
||||
assert(loginAction().type === 'LOGIN');
|
||||
```
|
||||
|
||||
store 应该用属性 `login` 设置为 `false` 的对象初始化。
|
||||
|
||||
```js
|
||||
assert(store.getState().login === false);
|
||||
```
|
||||
|
||||
dispatch `loginAction` 后应将 store 中 state 的 `login` 值更新为 `true`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
store.dispatch(loginAction());
|
||||
const afterState = store.getState();
|
||||
return initialState.login === false && afterState.login === true;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
如果 action 的类型不是 `LOGIN`,那么 store 应返回当前的 state。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
store.dispatch({ type: '__TEST__ACTION__' });
|
||||
let afterTest = store.getState();
|
||||
return typeof afterTest === 'object' && afterTest.hasOwnProperty('login');
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const defaultState = {
|
||||
login: false
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
};
|
||||
|
||||
const store = Redux.createStore(reducer);
|
||||
|
||||
const loginAction = () => {
|
||||
return {
|
||||
type: 'LOGIN'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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'
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,133 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036158
|
||||
title: 永不改变状态
|
||||
challengeType: 6
|
||||
forumTopicId: 301445
|
||||
dashedName: never-mutate-state
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
这些最后的挑战描述了在 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`,因为它们经常用来描述一些代表信息的数据结构。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代码编辑器中有一个 `store` 和 `reducer`,用于管理待办事项。 完成 reducer 里的 `ADD_TO_DO` 用例,使其可以将一个新的待办事项附加到 state。 使用通过标准 JavaScript 或 ES6 的一些方法实现此目的。 看看是否可以找到一种方法来返回一个新数组,其中来自 `action.todo` 的项目添加到数组的末尾。
|
||||
|
||||
# --hints--
|
||||
|
||||
Redux store 应该在代码编辑器中存在并使用名字为 `todos` 的数组进行状态初始化。
|
||||
|
||||
```js
|
||||
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(',')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`addToDo` 和 `immutableReducer` 都应该是函数。
|
||||
|
||||
```js
|
||||
assert(typeof addToDo === 'function' && typeof immutableReducer === 'function');
|
||||
```
|
||||
|
||||
在 Redux store 上 dispatch 一个类型为 `ADD_TO_DO` 的 aciton,应该添加一个 `todo` 项,并且不应该改变 state。
|
||||
|
||||
```js
|
||||
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);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const ADD_TO_DO = 'ADD_TO_DO';
|
||||
|
||||
// A list of strings representing tasks 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:
|
||||
// Don't mutate state here or the tests will fail
|
||||
return
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const addToDo = (todo) => {
|
||||
return {
|
||||
type: ADD_TO_DO,
|
||||
todo
|
||||
}
|
||||
}
|
||||
|
||||
const store = Redux.createStore(immutableReducer);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const addToDo = (todo) => {
|
||||
return {
|
||||
type: ADD_TO_DO,
|
||||
todo
|
||||
}
|
||||
}
|
||||
|
||||
const store = Redux.createStore(immutableReducer);
|
||||
```
|
@ -0,0 +1,118 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036153
|
||||
title: 注册 Store 监听器
|
||||
challengeType: 6
|
||||
forumTopicId: 301446
|
||||
dashedName: register-a-store-listener
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
在 Redux `store` 对象上访问数据的另一种方法是 `store.subscribe()`。 这允许将监听器函数订阅到 store,只要 action 被 dispatch 就会调用它们。 这个方法的一个简单用途是为 store 订阅一个函数,它只是在每次收到一个 action 并且更新 store 时记录一条消息。
|
||||
|
||||
# --instructions--
|
||||
|
||||
编写一个回调函数,每次 store 收到一个 action 时,它会递增全局变量 `count`,并将此函数传递给 `store.subscribe()` 方法。 将会看到 `store.dispatch()` 连续三次被调用,每次都直接传入一个 action 对象。 观察 dispatch action 之间的控制台输出,看看是否发生了更新。
|
||||
|
||||
# --hints--
|
||||
|
||||
在 store 上 dispatch `ADD` action 应该使计数器增加 `1`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
store.dispatch({ type: 'ADD' });
|
||||
const newState = store.getState();
|
||||
return newState === initialState + 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
应该有一个监听函数 `store.subscribe` 订阅 store。
|
||||
|
||||
```js
|
||||
(getUserInput) => assert(getUserInput('index').match(/store\s*\.\s*subscribe\(/gm));
|
||||
```
|
||||
|
||||
`store.subscribe` 应该收到一个函数。
|
||||
|
||||
```js
|
||||
(getUserInput) => assert(getUserInput('index').match(/(\s*function\s*)|(\s*\(\s*\)\s*=>)/gm))
|
||||
```
|
||||
|
||||
在更新 store 时,`store.subscribe` 应该在回调中使全局变量 `count` 的值增加。
|
||||
|
||||
```js
|
||||
assert(store.getState() === count);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --before-user-code--
|
||||
|
||||
```js
|
||||
count = 0;
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const ADD = 'ADD';
|
||||
|
||||
const reducer = (state = 0, action) => {
|
||||
switch(action.type) {
|
||||
case ADD:
|
||||
return state + 1;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
store.dispatch({type: ADD});
|
||||
console.log(count);
|
||||
store.dispatch({type: ADD});
|
||||
console.log(count);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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;
|
||||
// Change code below this line
|
||||
|
||||
store.subscribe( () =>
|
||||
{
|
||||
count++;
|
||||
}
|
||||
);
|
||||
|
||||
// Change code above this line
|
||||
|
||||
store.dispatch({type: ADD});
|
||||
store.dispatch({type: ADD});
|
||||
store.dispatch({type: ADD});
|
||||
```
|
@ -0,0 +1,114 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403615a
|
||||
title: 从数组中删除项目
|
||||
challengeType: 6
|
||||
forumTopicId: 301447
|
||||
dashedName: remove-an-item-from-an-array
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
是时候练习从数组中删除项目了。 扩展运算符也可以在这里使用。 其他有用的JavaScript方法包括 `slice()` 和 `concat()`。
|
||||
|
||||
# --instructions--
|
||||
|
||||
reducer 和 action creator 被修改为根据项目的索引从数组中删除一个项目。 完成编写 reducer 以便返回一个新的状态数组,并删除特定索引处的项目。
|
||||
|
||||
# --hints--
|
||||
|
||||
Redux store 应该存在并初始化一个 `[0,1,2,3,4,5]` 的状态。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
return (
|
||||
Array.isArray(initialState) === true &&
|
||||
DeepEqual(initialState, [0, 1, 2, 3, 4, 5])
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`removeItem` 和 `immutableReducer` 都应该是一个函数。
|
||||
|
||||
```js
|
||||
assert(
|
||||
typeof removeItem === 'function' && typeof immutableReducer === 'function'
|
||||
);
|
||||
```
|
||||
|
||||
dispatch `removeItem` action creator 应该从 state 中删除项目,不应该改变 state。
|
||||
|
||||
```js
|
||||
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])
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
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
|
||||
return
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const removeItem = (index) => {
|
||||
return {
|
||||
type: 'REMOVE_ITEM',
|
||||
index
|
||||
}
|
||||
}
|
||||
|
||||
const store = Redux.createStore(immutableReducer);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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)
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const removeItem = (index) => {
|
||||
return {
|
||||
type: 'REMOVE_ITEM',
|
||||
index
|
||||
}
|
||||
}
|
||||
|
||||
const store = Redux.createStore(immutableReducer);
|
||||
```
|
@ -0,0 +1,107 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036155
|
||||
title: 发送 Action Data 给 Store
|
||||
challengeType: 6
|
||||
forumTopicId: 301448
|
||||
dashedName: send-action-data-to-the-store
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
到目前为止,你已经学会了如何将 action dispatch 给 Redux store,但到目前为止,这些 action 并未包含除 `type`之外的任何信息。 还可以和 action 一起发送特定数据。 事实上,这是非常常见的,因为 action 通常源于一些用户交互,并且往往会携带一些数据, Redux store 经常需要知道这些数据。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在代码编辑器中定义了一个基础的 `notesReducer()` 和 `addNoteText()` action creator。 完成 `addNoteText()` 函数的主体,这样它就会返回一个 `action` 对象。 该对象应该包含一个 `type` 属性,其值为 `ADD_NOTE`,还有一个传入 action creator 的属性为 `text` 的 `note` 数据。 当调用 action creator 时,需要传入可以访问该对象的特定笔记信息。
|
||||
|
||||
接下来,完成在 `notesReducer()` 中编写的 `switch` 语句。 需要添加一个处理 `addNoteText()` 操作的选项。 如果 action 的类型为 `ADD_NOTE`,就应该触发这个 case,并且它应该在传入的 `action` 上返回 `text` 属性作为新的 `state`
|
||||
|
||||
这个 action 将在代码底部发送。 一旦完成后,运行代码并观察控制台。 这就是将特定于 action 的数据发送到 store 并在更新 store `state`时使用它所需的全部内容。
|
||||
|
||||
# --hints--
|
||||
|
||||
action creator `addNoteText` 应该返回一个包含 `type` 和 `text` 的对象。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const addNoteFn = addNoteText('__TEST__NOTE');
|
||||
return addNoteFn.type === ADD_NOTE && addNoteFn.text === '__TEST__NOTE';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
dispatch 一个 action creator 是 `addNoteText` 的action `ADD_NOTE`,应将 `state` 更新为 action creator 传递的字符串。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
store.dispatch(addNoteText('__TEST__NOTE'));
|
||||
const newState = store.getState();
|
||||
return initialState !== newState && newState === '__TEST__NOTE';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
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);
|
||||
|
||||
console.log(store.getState());
|
||||
store.dispatch(addNoteText('Hello!'));
|
||||
console.log(store.getState());
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
const ADD_NOTE = 'ADD_NOTE';
|
||||
|
||||
const notesReducer = (state = 'Initial State', action) => {
|
||||
switch(action.type) {
|
||||
// Change code below this line
|
||||
case ADD_NOTE:
|
||||
return action.text;
|
||||
// Change code above this line
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const addNoteText = (note) => {
|
||||
// Change code below this line
|
||||
return {
|
||||
type: ADD_NOTE,
|
||||
text: note
|
||||
}
|
||||
// Change code above this line
|
||||
};
|
||||
|
||||
const store = Redux.createStore(notesReducer);
|
||||
|
||||
console.log(store.getState());
|
||||
store.dispatch(addNoteText('Hello Redux!'));
|
||||
console.log(store.getState());
|
||||
```
|
@ -0,0 +1,152 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036151
|
||||
title: 使用 Switch 语句处理多个 Actions
|
||||
challengeType: 6
|
||||
forumTopicId: 301449
|
||||
dashedName: use-a-switch-statement-to-handle-multiple-actions
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
可以定义 Redux store 处理多种 action 类型。 假设在 Redux store 中管理用户身份验证。 希望用状态表示用户登录和注销。 使用 state 的 `authenticated` 属性表示它。 还需要使用 action creators 创建与用户登录和用户注销相对应的 action,以及 action 对象本身。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代码编辑器为你创建了 store、actions、action creators。 通过编写 `reducer` 函数来处理多个身份验证操作。 可以在 `reducer` 里通过使用 JavaScript 的 `switch` 来响应不同的 action 事件。 这是编写 Redux reducers 的标准模式。 switch 语句应该切换 `action.type` 并返回适当的身份验证状态。
|
||||
|
||||
**注意:** 此时,不要担心 state 的不变性,因为在这个示例中它很小而且很简单。 所以对于每个操作都可以返回一个新对象,比如 `{authenticated: true}`。 另外,不要忘记在 switch 语句中写一个 `default` case,返回当前的 `state`。 这是很重要的,因为当程序有多个 reducer,当每一个 action 被 dispatch 时它们都会运行,即使 action 与该 reducer 无关。 在这种情况下,你要确保返回当前的 `state`
|
||||
|
||||
# --hints--
|
||||
|
||||
调用函数 `loginUser` 应该返回一个 type 属性设置为字符串 `LOGIN` 的对象。
|
||||
|
||||
```js
|
||||
assert(loginUser().type === 'LOGIN');
|
||||
```
|
||||
|
||||
调用函数 `logoutUser` 应该返回一个 type 属性设置为字符串 `LOGOUT` 的对象。
|
||||
|
||||
```js
|
||||
assert(logoutUser().type === 'LOGOUT');
|
||||
```
|
||||
|
||||
store 应该设置一个 `authenticated` 属性设置为 `false` 的初始化对象。
|
||||
|
||||
```js
|
||||
assert(store.getState().authenticated === false);
|
||||
```
|
||||
|
||||
dispatch `loginUser`应该将 store 中的 state 的 `authenticated` 值更新为 `true`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
store.dispatch(loginUser());
|
||||
const afterLogin = store.getState();
|
||||
return (
|
||||
initialState.authenticated === false && afterLogin.authenticated === true
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
dispatch `logoutUser` 应该将 store 中的 state 的 `authenticated` 值更新为 `false`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
store.dispatch(loginUser());
|
||||
const loggedIn = store.getState();
|
||||
store.dispatch(logoutUser());
|
||||
const afterLogout = store.getState();
|
||||
return (
|
||||
loggedIn.authenticated === true && afterLogout.authenticated === false
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`authReducer` 函数应该使用 `switch` 语句处理多个 action 类型。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
getUserInput('index').toString().includes('switch') &&
|
||||
getUserInput('index').toString().includes('case') &&
|
||||
getUserInput('index').toString().includes('default')
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const defaultState = {
|
||||
authenticated: false
|
||||
};
|
||||
|
||||
const authReducer = (state = defaultState, action) => {
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
};
|
||||
|
||||
const store = Redux.createStore(authReducer);
|
||||
|
||||
const loginUser = () => {
|
||||
return {
|
||||
type: 'LOGIN'
|
||||
}
|
||||
};
|
||||
|
||||
const logoutUser = () => {
|
||||
return {
|
||||
type: 'LOGOUT'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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'
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,205 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036152
|
||||
title: 使用 const 声明 Action Types
|
||||
challengeType: 6
|
||||
forumTopicId: 301450
|
||||
dashedName: use-const-for-action-types
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
在使用 Redux 时的一个常见做法是将操作类型指定为只读,然后在任何使用它们的地方引用这些常量。 可以通过将 action types 使用 `const` 声明重构你正在使用的代码。
|
||||
|
||||
# --instructions--
|
||||
|
||||
将 `LOGIN` 和 `LOGOUT` 声明为 `const` 的值,并为它们分别分配字符串 `'LOGIN'` 和 `'LOGOUT'`。 然后,编辑 `authReducer()` 和 action creators 来引用这些常量而不是字符串值。
|
||||
|
||||
**注意:** 通常以全部大写形式写出常量,这也是 Redux 的标准做法。
|
||||
|
||||
# --hints--
|
||||
|
||||
调用函数 `loginUser` 应该返回一个 `type` 属性设置为字符串 `LOGIN` 的对象。
|
||||
|
||||
```js
|
||||
assert(loginUser().type === 'LOGIN');
|
||||
```
|
||||
|
||||
调用函数 `logoutUser` 应该返回一个 `type` 属性设置为字符串 `LOGOUT` 的对象。
|
||||
|
||||
```js
|
||||
assert(logoutUser().type === 'LOGOUT');
|
||||
```
|
||||
|
||||
store 应该用属性 `login` 设置为 `false` 的对象初始化。
|
||||
|
||||
```js
|
||||
assert(store.getState().authenticated === false);
|
||||
```
|
||||
|
||||
dispatch `loginUser` 以后应将 store 中的 state 的 `login` 值更新为 `true`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
store.dispatch(loginUser());
|
||||
const afterLogin = store.getState();
|
||||
return (
|
||||
initialState.authenticated === false && afterLogin.authenticated === true
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
dispatch `logoutUser` 应将 store 中的 state 的 `login` 值更新为 `false`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
store.dispatch(loginUser());
|
||||
const loggedIn = store.getState();
|
||||
store.dispatch(logoutUser());
|
||||
const afterLogout = store.getState();
|
||||
return (
|
||||
loggedIn.authenticated === true && afterLogout.authenticated === false
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`authReducer` 函数应该使用 switch 语句处理多个 action 类型。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
return (
|
||||
typeof authReducer === 'function' &&
|
||||
getUserInput('index').toString().includes('switch') &&
|
||||
getUserInput('index').toString().includes('case') &&
|
||||
getUserInput('index').toString().includes('default')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`LOGIN` 和 `LOGOUT` 应该声明为 `const` 值,并且应该分配为 `LOGIN` 和 `LOGOUT` 的字符串。
|
||||
|
||||
```js
|
||||
const noWhiteSpace = __helpers.removeWhiteSpace(code);
|
||||
assert(
|
||||
(/constLOGIN=(['"`])LOGIN\1/.test(noWhiteSpace) &&
|
||||
/constLOGOUT=(['"`])LOGOUT\1/.test(noWhiteSpace)) ||
|
||||
/const(LOGIN|LOGOUT)=(['"`])\1\2,(?!\1)(LOGIN|LOGOUT)=(['"`])\3\4/.test(noWhiteSpace)
|
||||
);
|
||||
```
|
||||
|
||||
action creator 和 reducer 中应该引用 `LOGIN` 和 `LOGOUT` 常量。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
const noWhiteSpace = __helpers.removeWhiteSpace(
|
||||
getUserInput('index').toString()
|
||||
);
|
||||
return (
|
||||
noWhiteSpace.includes('caseLOGIN:') &&
|
||||
noWhiteSpace.includes('caseLOGOUT:') &&
|
||||
noWhiteSpace.includes('type:LOGIN') &&
|
||||
noWhiteSpace.includes('type:LOGOUT')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
|
||||
|
||||
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'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,170 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036156
|
||||
title: 使用中间件处理异步操作
|
||||
challengeType: 6
|
||||
forumTopicId: 301451
|
||||
dashedName: use-middleware-to-handle-asynchronous-actions
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
目前为止的挑战都在避免讨论异步操作,但它们是 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,中间件就可以处理其余的部分。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在 `handleAsync()` 的 action creator 中编写两个 dispatch 事件。 在 `setTimeout()`(模拟 API 调用)之前 dispatch `requestingData()`。 然后在收到(模拟)数据后,dispatch `receivedData()` action,传入这些数据。 现在已经知道如何处理 Redux 中的异步操作, 其他所有操作都继续像以前一样。
|
||||
|
||||
# --hints--
|
||||
|
||||
`requestingData` action creator 应返回类型和值都为 `REQUESTING_DATA` 的对象。
|
||||
|
||||
```js
|
||||
assert(requestingData().type === REQUESTING_DATA);
|
||||
```
|
||||
|
||||
`receivedData` action creator 应返回类型和值都等于 `RECEIVED_DATA` 的对象。
|
||||
|
||||
```js
|
||||
assert(receivedData('data').type === RECEIVED_DATA);
|
||||
```
|
||||
|
||||
`asyncDataReducer` 必须是个函数。
|
||||
|
||||
```js
|
||||
assert(typeof asyncDataReducer === 'function');
|
||||
```
|
||||
|
||||
分发 `requestingData` 后 action creator 应该将 store 中 `state` 的 fetching 值更新为 `true`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
store.dispatch(requestingData());
|
||||
const reqState = store.getState();
|
||||
return initialState.fetching === false && reqState.fetching === true;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
如果要 dispatch `handleAsync` 应先 dispatch 数据请求的 action,然后在收到请求结果后再 dispatch 接收的数据 action。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const noWhiteSpace = __helpers.removeWhiteSpace(handleAsync.toString());
|
||||
return (
|
||||
noWhiteSpace.includes('dispatch(requestingData())') === true &&
|
||||
noWhiteSpace.includes('dispatch(receivedData(data))') === true
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
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 request action here
|
||||
|
||||
setTimeout(function() {
|
||||
let data = {
|
||||
users: ['Jeff', 'William', 'Alice']
|
||||
}
|
||||
// Dispatch received data action here
|
||||
|
||||
}, 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)
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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)
|
||||
);
|
||||
```
|
@ -0,0 +1,114 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036159
|
||||
title: 在数组中使用扩展运算符
|
||||
challengeType: 6
|
||||
forumTopicId: 301452
|
||||
dashedName: use-the-spread-operator-on-arrays
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
ES6 中有助于在 Redux 中强制执行状态不变性的一个解决方案是扩展运算符:`...`。 扩展运算符具有很多的应用,其中一种非常适合通过一个已有的数组生成一个新数组。 这是相对较新的但常用的语法。 例如,如果你有一个数组 `myArray` 并写:
|
||||
|
||||
```js
|
||||
let newArray = [...myArray];
|
||||
```
|
||||
|
||||
`newArray` 现在是 `myArray` 的克隆。 两个数组仍然分别存在于内存中。 如果你执行像 `newArray.push(5)` 这样的变异, `myArray` 不会改变。 `...` 有效将 `myArray` 中的值*展开*到一个新数组中。 要克隆数组,但在新数组中添加其他值,可以编写 `[...myArray, 'new value']`。 这将返回一个由 `myArray` 中的值和字符串 `new value`(作为最后一个值)组成的新数组。 扩展语法可以像这样在数组组合中多次使用,但重要的是要注意它只是生成数组的浅拷贝副本。 也就是说,它只为一维数组提供不可变的数组操作。
|
||||
|
||||
# --instructions--
|
||||
|
||||
添加待办事项时,使用 spread 运算符返回新的状态副本。
|
||||
|
||||
# --hints--
|
||||
|
||||
Redux store 应该在代码编辑器中存在,并使用 `["Do not mutate state!"]` 进行状态初始化。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
return (
|
||||
Array.isArray(initialState) === true &&
|
||||
initialState[0] === 'Do not mutate state!'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`addToDo` 和 `immutableReducer` 都应该是函数。
|
||||
|
||||
```js
|
||||
assert(typeof addToDo === 'function' && typeof immutableReducer === 'function');
|
||||
```
|
||||
|
||||
在 Redux store 上 dispatch 一个类型为 `ADD_TO_DO` 的 aciton,应该添加一个 `todo` 项,并且不应该改变 state。
|
||||
|
||||
```js
|
||||
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);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
应使用扩展运算符返回新的 state。
|
||||
|
||||
```js
|
||||
(getUserInput) => assert(getUserInput('index').includes('...state'));
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
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
|
||||
return
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const addToDo = (todo) => {
|
||||
return {
|
||||
type: 'ADD_TO_DO',
|
||||
todo
|
||||
}
|
||||
}
|
||||
|
||||
const store = Redux.createStore(immutableReducer);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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);
|
||||
```
|
@ -0,0 +1,116 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036157
|
||||
title: 用 Redux 写一个计数器
|
||||
challengeType: 6
|
||||
forumTopicId: 301453
|
||||
dashedName: write-a-counter-with-redux
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
现在已经了解了 Redux 的所有核心原则! 已经了解了如何创建 action 和 action creator,创建 Redux store,通过 store dispatch action,以及使用纯粹的 reducer 设计状态更新。 甚至已经看到过如何使用 reducer 组合管理复杂状态并处理异步操作。 这些例子很简单,但这些概念是 Redux 的核心原则。 如果已经理解这些,那么就可以开始构建自己的 Redux 应用了。 接下来的挑战包括关于 `state` 不变性的一些细节,但是,这里是对到目前为止学到的所有内容的回顾。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在本课程中,将从头开始使用 Redux 实现一个简单的计数器。 基本知识在代码编辑器中提供,但你必须完成细节! 使用提供的名称定义 `incAction` 和 `decAction` action creator,`counterReducer()`,`INCREMENT`和`DECREMENT` action 类型,以及 Redux `store`。 一旦完成,应该能够 dispatch `INCREMENT` 或 `DECREMENT` 动作来增加或减少 `store` 中保存的状态。 开始构建你的第一个 Redux 应用程序吧,编码愉快!
|
||||
|
||||
# --hints--
|
||||
|
||||
action creator `incAction` 应该返回一个 `type` 等于 `INCREMENT` 的 action 对象。
|
||||
|
||||
```js
|
||||
assert(incAction().type === INCREMENT);
|
||||
```
|
||||
|
||||
action creator `decAction` 应该返回一个 `type` 等于 `DECREMENT` 的 action 对象。
|
||||
|
||||
```js
|
||||
assert(decAction().type === DECREMENT);
|
||||
```
|
||||
|
||||
Redux store 应该将 `state` 初始化为 0。
|
||||
|
||||
```js
|
||||
assert(store.getState() === 0);
|
||||
```
|
||||
|
||||
在 Redux store 上 dispatch `incAction` 应该将 `state` 增加 1。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
store.dispatch(incAction());
|
||||
const incState = store.getState();
|
||||
return initialState + 1 === incState;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
在 Redux store 上 dispatch `decAction` 应该将 `state` 减少 1。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
store.dispatch(decAction());
|
||||
const decState = store.getState();
|
||||
return initialState - 1 === decState;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`counterReducer` 必须是一个函数。
|
||||
|
||||
```js
|
||||
assert(typeof counterReducer === 'function');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
const INCREMENT = null; // Define a constant for increment action types
|
||||
const DECREMENT = null; // Define a constant for decrement action types
|
||||
|
||||
const counterReducer = null; // Define the counter reducer which will increment or decrement the state based on the action it receives
|
||||
|
||||
const incAction = null; // Define an action creator for incrementing
|
||||
|
||||
const decAction = null; // Define an action creator for decrementing
|
||||
|
||||
const store = null; // Define the Redux store here, passing in your reducers
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
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);
|
||||
```
|
Reference in New Issue
Block a user