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