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,165 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403616e
|
||||
title: 使用 this.props 訪問 Props
|
||||
challengeType: 6
|
||||
forumTopicId: 301375
|
||||
dashedName: access-props-using-this-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
前幾項挑戰涵蓋了將 props 傳遞給子組件的基本方法。 但是,倘若接收 prop 的子組件不是無狀態函數組件,而是一個 ES6 類組件又當如何呢? ES6 類組件訪問 props 的方法略有不同。
|
||||
|
||||
任何時候,如果要引用類組件本身,可以使用 `this` 關鍵字。 要訪問類組件中的 props,需要在在訪問它的代碼前面添加 `this`。 例如,如果 ES6 類組件有一個名爲 `data` 的 prop,可以在 JSX 中這樣寫:`{this.props.data}`。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在父組件 `ResetPassword` 中渲染 `ReturnTempPassword` 組件的一個實例。 在這裏,爲 `ReturnTempPassword` 提供一個 `tempPassword` prop,並賦值一個長度至少爲 8 個字符的字符串。 在子組件 `ReturnTempPassword` 中,訪問 `strong` 標籤中的 `tempPassword` prop,以確保用戶看到臨時密碼。
|
||||
|
||||
# --hints--
|
||||
|
||||
`ResetPassword` 組件應該返回單個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ResetPassword));
|
||||
return mockedComponent.children().type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`ResetPassword` 的第四個子組件應該是 `ReturnTempPassword` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ResetPassword));
|
||||
return (
|
||||
mockedComponent.children().childAt(3).name() === 'ReturnTempPassword'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`ReturnTempPassword` 組件應該有一個名爲 `tempPassword` 的屬性。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ResetPassword));
|
||||
return mockedComponent.find('ReturnTempPassword').props().tempPassword;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`ReturnTempPassword` 組件的 `tempPassword` prop 值應該是一個字符串,至少爲 8 個字符。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ResetPassword));
|
||||
const temp = mockedComponent.find('ReturnTempPassword').props()
|
||||
.tempPassword;
|
||||
return typeof temp === 'string' && temp.length >= 8;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`ReturnTempPassword` 組件應該顯示作爲 `tempPassword` prop 創建的密碼,並且密碼被包裹在 `strong` 標籤中。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ResetPassword));
|
||||
return (
|
||||
mockedComponent.find('strong').text() ===
|
||||
mockedComponent.find('ReturnTempPassword').props().tempPassword
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<ResetPassword />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class ReturnTempPassword extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<p>Your temporary password is: <strong></strong></p>
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class ResetPassword extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Reset Password</h2>
|
||||
<h3>We've generated a new temporary password for you.</h3>
|
||||
<h3>Please reset this password from your account settings ASAP.</h3>
|
||||
{ /* Change code below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class ReturnTempPassword extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<p>Your temporary password is: <strong>{this.props.tempPassword}</strong></p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class ResetPassword extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Reset Password</h2>
|
||||
<h3>We've generated a new temporary password for you.</h3>
|
||||
<h3>Please reset this password from your account settings ASAP.</h3>
|
||||
{ /* Change code below this line */ }
|
||||
<ReturnTempPassword tempPassword="serrPbqrPnzc" />
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,82 @@
|
||||
---
|
||||
id: 5a24bbe0dba28a8d3cbd4c5e
|
||||
title: 在 JSX 中添加註釋
|
||||
challengeType: 6
|
||||
forumTopicId: 301376
|
||||
dashedName: add-comments-in-jsx
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
JSX 是一種可以編譯成 JavaScript 的語法。 有時,爲了便於閱讀,可能需要在代碼中添加註釋。 像大多數編程語言一樣,JSX 也有自己的方法來實現這一點。
|
||||
|
||||
要將註釋放在 JSX 中,可以使用 `{/* */}` 語法來包裹註釋文本。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器中的 JSX 元素與在上一個挑戰中創建的元素類似。 在提供的 `div` 元素裏添加註釋,不修改現有的 `h1` 或 `p` 元素。
|
||||
|
||||
# --hints--
|
||||
|
||||
常量 `JSX` 應該返回一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(JSX.type === 'div');
|
||||
```
|
||||
|
||||
`div` 應該包含一個 `h1` 標籤作爲第一個元素。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[0].type === 'h1');
|
||||
```
|
||||
|
||||
`div` 應該包含一個 `p` 標籤作爲第二個元素。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[1].type === 'p');
|
||||
```
|
||||
|
||||
當前的 `h1` 和 `p` 元素不能被修改。
|
||||
|
||||
```js
|
||||
assert(
|
||||
JSX.props.children[0].props.children === 'This is a block of JSX' &&
|
||||
JSX.props.children[1].props.children === "Here's a subtitle"
|
||||
);
|
||||
```
|
||||
|
||||
`JSX` 應該包含一個註釋。
|
||||
|
||||
```js
|
||||
assert(/<div>[\s\S]*{\s*\/\*[\s\S]*\*\/\s*}[\s\S]*<\/div>/.test(code));
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(JSX, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const JSX = (
|
||||
<div>
|
||||
<h1>This is a block of JSX</h1>
|
||||
<p>Here's a subtitle</p>
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const JSX = (
|
||||
<div>
|
||||
<h1>This is a block of JSX</h1>
|
||||
{ /* this is a JSX comment */ }
|
||||
<p>Here's a subtitle</p>
|
||||
</div>);
|
||||
```
|
@ -0,0 +1,179 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403617e
|
||||
title: 添加事件偵聽器
|
||||
challengeType: 6
|
||||
forumTopicId: 301377
|
||||
dashedName: add-event-listeners
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`componentDidMount()` 方法也是添加特定功能所需的任何事件監聽器的最佳位置。 React 提供了一個合成事件系統,它封裝了瀏覽器中的事件系統。 這意味着,不管用戶用的是什麼瀏覽器,合成事件系統的行爲都完全相同 -- 即使不同瀏覽器之間的本地事件的行爲可能不同。
|
||||
|
||||
之前已經接觸了一些合成事件處理程序,如`onClick()`。 React 的合成事件系統非常適合用於在 DOM 元素上管理的大多數交互。 但是,如果要將事件處理程序附加到 document 或 window 對象,則必須直接執行此操作。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在 `componentDidMount()` 方法中爲 `keydown` 事件添加事件監聽器,並讓這些事件觸發回調 `handleKeyPress()`。 可以使用 `document.addEventListener()`,它將事件(用引號括起來)作爲第一個參數,將回調作爲第二個參數。
|
||||
|
||||
然後,在 `componentWillUnmount()` 中移除相同的事件監聽器。 可以把相同的參數傳遞給 `document.removeEventListener()`。 在卸載和銷燬 React 組件之前,最好在這個生命週期方法中對它們進行清理。 移除事件監聽器就是這樣一個清理操作的例子。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該渲染一個包含 `h1` 標籤的 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return mockedComponent.find('div').children().find('h1').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應該在 `componentDidMount` 中將 `keydown` 事件監聽添加到到 document 上。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const didMountString = mockedComponent
|
||||
.instance()
|
||||
.componentDidMount.toString();
|
||||
return new RegExp(
|
||||
'document.addEventListener(.|\n|\r)+keydown(.|\n|\r)+this.handleKeyPress'
|
||||
).test(didMountString);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應該在 `componentWillUnmount` 中將 document 上的 `keydown` 事件監聽移除。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const willUnmountString = mockedComponent
|
||||
.instance()
|
||||
.componentWillUnmount.toString();
|
||||
return new RegExp(
|
||||
'document.removeEventListener(.|\n|\r)+keydown(.|\n|\r)+this.handleKeyPress'
|
||||
).test(willUnmountString);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
當組件裝載完畢,按 `enter` 鍵應該會更新其 state ,並渲染到 `h1` 標籤。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const beforeState = mockedComponent.state('message');
|
||||
const beforeText = mockedComponent.find('h1').text();
|
||||
const pressEnterKey = () => {
|
||||
mockedComponent.instance().handleKeyPress({ keyCode: 13 });
|
||||
return waitForIt(() => {
|
||||
mockedComponent.update();
|
||||
return {
|
||||
state: mockedComponent.state('message'),
|
||||
text: mockedComponent.find('h1').text()
|
||||
};
|
||||
});
|
||||
};
|
||||
const afterKeyPress = await pressEnterKey();
|
||||
assert(
|
||||
beforeState !== afterKeyPress.state && beforeText !== afterKeyPress.text
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
message: ''
|
||||
};
|
||||
this.handleEnter = this.handleEnter.bind(this);
|
||||
this.handleKeyPress = this.handleKeyPress.bind(this);
|
||||
}
|
||||
// Change code below this line
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
componentWillUnmount() {
|
||||
|
||||
}
|
||||
// Change code above this line
|
||||
handleEnter() {
|
||||
this.setState((state) => ({
|
||||
message: state.message + 'You pressed the enter key! '
|
||||
}));
|
||||
}
|
||||
handleKeyPress(event) {
|
||||
if (event.keyCode === 13) {
|
||||
this.handleEnter();
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>{this.state.message}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
message: ''
|
||||
};
|
||||
this.handleKeyPress = this.handleKeyPress.bind(this);
|
||||
this.handleEnter = this.handleEnter.bind(this); }
|
||||
componentDidMount() {
|
||||
// Change code below this line
|
||||
document.addEventListener('keydown', this.handleKeyPress);
|
||||
// Change code above this line
|
||||
}
|
||||
componentWillUnmount() {
|
||||
// Change code below this line
|
||||
document.removeEventListener('keydown', this.handleKeyPress);
|
||||
// Change code above this line
|
||||
}
|
||||
handleEnter() {
|
||||
this.setState((state) => ({
|
||||
message: state.message + 'You pressed the enter key! '
|
||||
}));
|
||||
}
|
||||
handleKeyPress(event) {
|
||||
if (event.keyCode === 13) {
|
||||
this.handleEnter();
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>{this.state.message}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,112 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036182
|
||||
title: 在 React 中添加內聯樣式
|
||||
challengeType: 6
|
||||
forumTopicId: 301378
|
||||
dashedName: add-inline-styles-in-react
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
在上一次挑戰中,你可能已經注意到,除了設置爲 JavaScript 對象的 `style` 屬性之外,與 HTML 內聯樣式相比,React 的內聯樣式還有其他幾個語法差異。 首先,某些 CSS 樣式屬性的名稱使用駝峯式命名。 例如,最後一個挑戰用 `fontSize` 而不是 `font-size` 來設置字體的大小。 對於 JavaScript 對象屬性來說,像 `font-size` 這樣的連字符命名是無效的語法,所以 React 使用駝峯式命名。 通常,任何連字符的 style 屬性在 JSX 中都是使用駝峯式命名的。
|
||||
|
||||
除非另有規定,否則所有屬性值的 length(如`height`、`width` 和 `fontSize`)其單位都假定爲 `px`。 例如,如果要使用 `em`,可以用引號將值和單位括起來,例如 `{fontSize: "4em"}`。 除了默認爲 `px` 的 length 值之外,所有其他屬性值都應該用引號括起來。
|
||||
|
||||
# --instructions--
|
||||
|
||||
如果你有大量樣式,你可以將樣式 `object`(對象)分配給一個常量,以保持代碼的組織有序。 在文件頂部將你的樣式聲明爲全局變量。 定義一個 `styles` 常量,並將其聲明爲具有三個樣式屬性及對應值的 `object`(對象)。 使 `div` 的文字顏色爲 `purple`、字號爲 `40`、邊框爲 `2px solid purple`。 然後設置 `style` 屬性,使其等於 `styles` 常量。
|
||||
|
||||
# --hints--
|
||||
|
||||
`styles` 變量應該是具有三個屬性的 `object`(對象)。
|
||||
|
||||
```js
|
||||
assert(Object.keys(styles).length === 3);
|
||||
```
|
||||
|
||||
`styles` 變量的 `color` 屬性應該設置爲 `purple`。
|
||||
|
||||
```js
|
||||
assert(styles.color === 'purple');
|
||||
```
|
||||
|
||||
`styles` 變量應該將 `fontSize` 屬性設置爲 `40`。
|
||||
|
||||
```js
|
||||
assert(styles.fontSize === 40);
|
||||
```
|
||||
|
||||
`styles` 變量的 `border` 屬性應該設置爲 `2px solid purple`。
|
||||
|
||||
```js
|
||||
assert(styles.border === '2px solid purple');
|
||||
```
|
||||
|
||||
組件應該渲染一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.shallow(React.createElement(Colorful));
|
||||
return mockedComponent.type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`div` 元素的樣式應該由 `styles` 對象定義。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.shallow(React.createElement(Colorful));
|
||||
return (
|
||||
mockedComponent.props().style.color === 'purple' &&
|
||||
mockedComponent.props().style.fontSize === 40 &&
|
||||
mockedComponent.props().style.border === '2px solid purple'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<Colorful />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
// Change code above this line
|
||||
class Colorful extends React.Component {
|
||||
render() {
|
||||
// Change code below this line
|
||||
return (
|
||||
<div style={{color: "yellow", fontSize: 24}}>Style Me!</div>
|
||||
);
|
||||
// Change code above this line
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const styles = {
|
||||
color: "purple",
|
||||
fontSize: 40,
|
||||
border: "2px solid purple"
|
||||
};
|
||||
// Change code above this line
|
||||
class Colorful extends React.Component {
|
||||
render() {
|
||||
// Change code below this line
|
||||
return (
|
||||
<div style={styles}>Style Me!</div>
|
||||
);
|
||||
// Change code above this line
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,136 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036174
|
||||
title: 將 this 綁定到 Class 方法上
|
||||
challengeType: 6
|
||||
forumTopicId: 301379
|
||||
dashedName: bind-this-to-a-class-method
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
除了設置和更新 `state` 之外,還可以爲組件類定義方法。 類方法通常需要使用 `this` 關鍵字,以便它可以訪問方法中類的屬性(例如 `state` 和 `props`)。 有幾種方法可以讓類方法訪問 `this`。
|
||||
|
||||
一種常見的方法是在構造函數中顯式地綁定 `this`,這樣當組件初始化時,`this` 就會綁定到類方法。 你可能已經注意到上一個挑戰在構造函數中的 `handleClick` 方法使用了 `this.handleClick = this.handleClick.bind(this)`。 然後,當在類方法中調用像 `this.setState()` 這樣的函數時,`this` 指的是這個類,而不是 `undefined`。
|
||||
|
||||
**注意:** `this`關鍵字是 JavaScript 中最令人困惑的方面之一,但它在 React 中扮演着重要的角色。 雖然它的行爲在這裏是完全正常的,但是這些課程並不深入研究`this`,所以如果以上內容令你感到困惑,請參考其他課程!
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器有一個帶有 `state` 的組件,用於跟蹤項目計數。 它還有一個方法,允許設置文本爲 `You clicked!`。 但是,該方法不起作用,因爲它使用了未定義的 `this` 關鍵字。 可以通過將 `this` 顯式綁定到組件構造函數中的 `handleClick()`方法來修復它。
|
||||
|
||||
接下來,向 render 方法中的 `button` 元素添加一個單擊處理程序。 當按鈕接收到單擊事件時,它應該觸發 `handleClick()` 方法。 記住,傳遞給 `onClick` 處理程序的方法需要使用花括號,因爲它應該直接被解釋爲 JavaScript。
|
||||
|
||||
完成上述步驟後,可以單擊按鈕並看到 `You clicked!`。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應返回 `div` 元素,該元素按順序包含兩個元素,一個按鈕和一個 `h1` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MyComponent)).find('div').length === 1 &&
|
||||
Enzyme.mount(React.createElement(MyComponent))
|
||||
.find('div')
|
||||
.childAt(0)
|
||||
.type() === 'button' &&
|
||||
Enzyme.mount(React.createElement(MyComponent))
|
||||
.find('div')
|
||||
.childAt(1)
|
||||
.type() === 'h1'
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 的 state 應該使用鍵值對 `{ text: "Hello" }`,進行初始化。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('text') === 'Hello'
|
||||
);
|
||||
```
|
||||
|
||||
單擊 `button` 元素應該運行 `handleClick` 方法,並使 state `text` 爲 `You clicked!`。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const first = () => {
|
||||
mockedComponent.setState({ text: 'Hello' });
|
||||
return waitForIt(() => mockedComponent.state('text'));
|
||||
};
|
||||
const second = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return waitForIt(() => mockedComponent.state('text'));
|
||||
};
|
||||
const firstValue = await first();
|
||||
const secondValue = await second();
|
||||
assert(firstValue === 'Hello' && secondValue === 'You clicked!');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
text: "Hello"
|
||||
};
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
}
|
||||
handleClick() {
|
||||
this.setState({
|
||||
text: "You clicked!"
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<button>Click Me</button>
|
||||
{ /* Change code above this line */ }
|
||||
<h1>{this.state.text}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
text: "Hello"
|
||||
};
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick() {
|
||||
this.setState({
|
||||
text: "You clicked!"
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick = {this.handleClick}>Click Me</button>
|
||||
<h1>{this.state.text}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,176 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036189
|
||||
title: 根據組件狀態有條件地更改內聯 CSS
|
||||
challengeType: 6
|
||||
forumTopicId: 301380
|
||||
dashedName: change-inline-css-conditionally-based-on-component-state
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
此時,已經看到了一些條件渲染的應用程序和內聯樣式的使用。 這裏還有一個將這兩個主題結合在一起的例子。 你也可以根據 React 組件的 state 有條件地渲染 CSS。 要執行此操作,請檢查條件,如果滿足該條件,則修改在 render 方法中分配給 JSX 元素的樣式對象。
|
||||
|
||||
理解這個模式很重要,因爲相比傳統的方式(這在 jQuery 中非常常見),直接修改 DOM 元素來應用樣式的方法是一個戲劇性的轉變。 在該方法中,必須跟蹤元素何時更改並直接處理實際操作。 跟蹤更改可能變得很困難,可能會使 UI無法預測。 當根據一個條件設置一個樣式對象時,描述了 UI 作爲應用程序的狀態函數應當如何展現。 如此便有一個清晰的單向流動的信息流。 這是使用 React 編寫應用程序時的首選方法。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器有一個簡單的帶有邊框樣式的受控 input 組件。 如果用戶在輸入框中鍵入超過 15 個字符的文本,希望將此邊框變成紅色。 添加一個條件來檢查這一點,如果條件有效,則將 input 的邊框樣式設置爲`3px solid red`。 可以通過在 input 中輸入文本來檢測它。
|
||||
|
||||
# --hints--
|
||||
|
||||
`GateKeeper` 組件應該渲染一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(GateKeeper));
|
||||
return mockedComponent.find('div').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`GateKeeper` 組件應使用初始爲空字符串的 `input` 進行初始化 state。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(GateKeeper));
|
||||
return mockedComponent.state().input === '';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`GateKeeper` 組件應該渲染一個 `h3` 標籤和一個 `input` 標籤。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(GateKeeper));
|
||||
return (
|
||||
mockedComponent.find('h3').length === 1 &&
|
||||
mockedComponent.find('input').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`input` 標籤 `border` 屬性的樣式應該初始化爲 `1px solid black`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(GateKeeper));
|
||||
return (
|
||||
mockedComponent.find('input').props().style.border === '1px solid black'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
如果 state 中 input 的值超過 15 個字符,則 `input` 標籤的 border 樣式應爲 `3px solid red`。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(GateKeeper));
|
||||
const simulateChange = (el, value) =>
|
||||
el.simulate('change', { target: { value } });
|
||||
let initialStyle = mockedComponent.find('input').props().style.border;
|
||||
const state_1 = () => {
|
||||
mockedComponent.setState({ input: 'this is 15 char' });
|
||||
return waitForIt(() => mockedComponent.find('input').props().style.border);
|
||||
};
|
||||
const state_2 = () => {
|
||||
mockedComponent.setState({
|
||||
input: 'A very long string longer than 15 characters.'
|
||||
});
|
||||
return waitForIt(() => mockedComponent.find('input').props().style.border);
|
||||
};
|
||||
const style_1 = await state_1();
|
||||
const style_2 = await state_2();
|
||||
assert(
|
||||
initialStyle === '1px solid black' &&
|
||||
style_1 === '1px solid black' &&
|
||||
style_2 === '3px solid red'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<GateKeeper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class GateKeeper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ''
|
||||
};
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({ input: event.target.value })
|
||||
}
|
||||
render() {
|
||||
let inputStyle = {
|
||||
border: '1px solid black'
|
||||
};
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
return (
|
||||
<div>
|
||||
<h3>Don't Type Too Much:</h3>
|
||||
<input
|
||||
type="text"
|
||||
style={inputStyle}
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class GateKeeper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ''
|
||||
};
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({ input: event.target.value })
|
||||
}
|
||||
render() {
|
||||
let inputStyle = {
|
||||
border: '1px solid black'
|
||||
};
|
||||
if (this.state.input.length > 15) {
|
||||
inputStyle.border = '3px solid red';
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<h3>Don't Type Too Much:</h3>
|
||||
<input
|
||||
type="text"
|
||||
style={inputStyle}
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,199 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036166
|
||||
title: 組合 React 組件
|
||||
challengeType: 6
|
||||
forumTopicId: 301381
|
||||
dashedName: compose-react-components
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
隨着挑戰繼續,將組合使用更復雜的 React 組件和 JSX,有一點需要注意。 在其它組件中渲染 ES6 風格的類組件和渲染在過去幾個挑戰中使用的簡單組件沒有什麼不同。 可以在其它組件中渲染 JSX 元素、無狀態功能組件和 ES6 類組件。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在代碼編輯器中,`TypesOfFood` 組件已經渲染了一個名爲 `Vegetables` 的組件。 此外,還有上次挑戰中的 `Fruits` 組件。
|
||||
|
||||
在 `Fruits` 中嵌套兩個組件,首先 `NonCitrus`,然後是 `Citrus`, 這兩個組件都已經引入。 接下來,將 `Fruits` 類組件嵌到 `TypesOfFood` 組件中,位於 `h1` 標題下方和 `Vegetables` 上方。 結果應該是一系列嵌套的組件,它們使用兩種不同的組件類型。
|
||||
|
||||
# --hints--
|
||||
|
||||
`TypesOfFood` 組件應該返回單個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(TypesOfFood));
|
||||
return mockedComponent.children().type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`TypesOfFood` 組件應該返回 `Fruits` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(TypesOfFood));
|
||||
return mockedComponent.children().childAt(1).name() === 'Fruits';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Fruits` 組件應該返回 `NonCitrus` 組件和 `Citrus` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(TypesOfFood));
|
||||
return (
|
||||
mockedComponent.find('Fruits').children().find('NonCitrus').length ===
|
||||
1 &&
|
||||
mockedComponent.find('Fruits').children().find('Citrus').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`TypesOfFood` 組件應該返回 `Vegetables` 組件,且其位於 `Fruits` 組件之下。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(TypesOfFood));
|
||||
return mockedComponent.children().childAt(2).name() === 'Vegetables';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --before-user-code--
|
||||
|
||||
```jsx
|
||||
class NonCitrus extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h4>Non-Citrus:</h4>
|
||||
<ul>
|
||||
<li>Apples</li>
|
||||
<li>Blueberries</li>
|
||||
<li>Strawberries</li>
|
||||
<li>Bananas</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
class Citrus extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h4>Citrus:</h4>
|
||||
<ul>
|
||||
<li>Lemon</li>
|
||||
<li>Lime</li>
|
||||
<li>Orange</li>
|
||||
<li>Grapefruit</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
class Vegetables extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Vegetables:</h2>
|
||||
<ul>
|
||||
<li>Brussel Sprouts</li>
|
||||
<li>Broccoli</li>
|
||||
<li>Squash</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<TypesOfFood />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class Fruits extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Fruits:</h2>
|
||||
{ /* Change code below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class TypesOfFood extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Types of Food:</h1>
|
||||
{ /* Change code below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
<Vegetables />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class Fruits extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Fruits:</h2>
|
||||
{ /* Change code below this line */ }
|
||||
<NonCitrus />
|
||||
<Citrus />
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TypesOfFood extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Types of Food:</h1>
|
||||
{ /* Change code below this line */ }
|
||||
<Fruits />
|
||||
{ /* Change code above this line */ }
|
||||
<Vegetables />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,110 @@
|
||||
---
|
||||
id: 5a24bbe0dba28a8d3cbd4c5d
|
||||
title: 創建一個複雜的 JSX 元素
|
||||
challengeType: 6
|
||||
forumTopicId: 301382
|
||||
dashedName: create-a-complex-jsx-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
上一個挑戰是 JSX 的一個簡單示例,但 JSX 也可以表示更復雜的 HTML。
|
||||
|
||||
關於嵌套的 JSX,需要知道的一件重要的事情,那就是它必須返回單個元素。
|
||||
|
||||
這個父元素將包裹所有其他級別的嵌套元素。
|
||||
|
||||
例如,幾個作爲兄弟元素編寫的 JSX 元素而沒有父元素包裹將不會被轉換。
|
||||
|
||||
這裏是一個示例:
|
||||
|
||||
**有效的 JSX:**
|
||||
|
||||
```jsx
|
||||
<div>
|
||||
<p>Paragraph One</p>
|
||||
<p>Paragraph Two</p>
|
||||
<p>Paragraph Three</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**無效的 JSX:**
|
||||
|
||||
```jsx
|
||||
<p>Paragraph One</p>
|
||||
<p>Paragraph Two</p>
|
||||
<p>Paragraph Three</p>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
定義一個新的常量 `JSX`,渲染一個 `div`,其中依次包含以下元素:
|
||||
|
||||
一個 `h1`,一個 `p`,一個包含三個 `li` 項的無序列表。 可以在每個元素中包含任意文本。
|
||||
|
||||
**注意:** 當像這樣渲染多個元素時,可以把它們都用圓括號括起來,但是這並不是必須的。 另外,此挑戰使用 `div` 標籤把所有子元素包裹在裏面。 如果刪除 `div`,JSX 將不會編譯這些元素。 請記住這一點,因爲在 React 組件中返回 JSX 元素時也適用。
|
||||
|
||||
# --hints--
|
||||
|
||||
常量 `JSX` 應該返回一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(JSX.type === 'div');
|
||||
```
|
||||
|
||||
`div` 應該包含一個 `h1` 標籤作爲第一個元素。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[0].type === 'h1');
|
||||
```
|
||||
|
||||
`div`應該包含一個`p`標籤作爲第二個元素。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[1].type === 'p');
|
||||
```
|
||||
|
||||
`div` 應該包含一個 `ul` 標籤作爲第三個元素。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[2].type === 'ul');
|
||||
```
|
||||
|
||||
`ul` 應該包含三個 `li` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
JSX.props.children
|
||||
.filter((ele) => ele.type === 'ul')[0]
|
||||
.props.children.filter((ele) => ele.type === 'li').length === 3
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(JSX, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const JSX = (
|
||||
<div>
|
||||
<h1>Hello JSX!</h1>
|
||||
<p>Some info</p>
|
||||
<ul>
|
||||
<li>An item</li>
|
||||
<li>Another item</li>
|
||||
<li>A third item</li>
|
||||
</ul>
|
||||
</div>);
|
||||
```
|
@ -0,0 +1,134 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036164
|
||||
title: 用組合的方式創建一個 React 組件
|
||||
challengeType: 6
|
||||
forumTopicId: 301383
|
||||
dashedName: create-a-component-with-composition
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
現在來看看如何組合多個 React 組件。 想象一下,現在正在構建一個應用程序,並創建了三個組件:`Navbar`、`Dashboard` 和 `Footer`。
|
||||
|
||||
要將這些組件組合在一起,可以創建一個 `App` *父組件*,將這三個組件分別渲染成爲*子組件*。 要在 React 組件中渲染一個子組件,需要在 JSX 中包含作爲自定義 HTML 標籤編寫的組件名稱。 例如,在 `render` 方法中,可以這樣編寫:
|
||||
|
||||
```jsx
|
||||
return (
|
||||
<App>
|
||||
<Navbar />
|
||||
<Dashboard />
|
||||
<Footer />
|
||||
</App>
|
||||
)
|
||||
```
|
||||
|
||||
當 React 遇到一個自定義 HTML 標籤引用另一個組件的時(如本例所示,組件名稱包含在 `< />` 中),它在自定義標籤的位置渲染該組件的標籤。 這可以說明 `App` 組件和 `Navbar`、`Dashboard` 以及 `Footer` 之間的父子關係。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在代碼編輯器中,有一個名爲 `ChildComponent` 的簡單功能組件和一個名爲 `ParentComponent` 的 React 組件。 通過在 `ParentComponent` 中渲染 `ChildComponent` 來將兩者組合在一起。 確保使用正斜槓關閉 `ChildComponent` 標籤。
|
||||
|
||||
**注意:** `ChildComponent` 是使用 ES6 的箭頭函數定義的,這是使用 React 時非常常見的做法。 但是,要知道這只是一個函數。 如果你不熟悉箭頭函數語法,請參閱 JavaScript 部分。
|
||||
|
||||
# --hints--
|
||||
|
||||
React 組件應該返回單個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
var shallowRender = Enzyme.shallow(React.createElement(ParentComponent));
|
||||
return shallowRender.type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
組件應該返回兩個嵌套的元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
var shallowRender = Enzyme.shallow(React.createElement(ParentComponent));
|
||||
return shallowRender.children().length === 2;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
組件的第二個子元素應該是 `ChildComponent`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ParentComponent));
|
||||
return (
|
||||
mockedComponent.find('ParentComponent').find('ChildComponent').length ===
|
||||
1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<ParentComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const ChildComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>I am the child</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class ParentComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>I am the parent</h1>
|
||||
{ /* Change code below this line */ }
|
||||
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const ChildComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>I am the child</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class ParentComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>I am the parent</h1>
|
||||
{ /* Change code below this line */ }
|
||||
<ChildComponent />
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,220 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036179
|
||||
title: 創建一個可以控制的表單
|
||||
challengeType: 6
|
||||
forumTopicId: 301384
|
||||
dashedName: create-a-controlled-form
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
上一個挑戰展示了 React 能控制某些元素的內部 state,比如 `input` 和 `textarea`,這使得這些元素成爲受控組件。 這也適用於其他表單元素,包括常規的 HTML 表單 `form` 元素。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`MyForm` 組件中是一個帶有提交處理程序的空 `form` 元素, 提交處理程序將在提交表單時被調用。
|
||||
|
||||
我們增加了一個提交表單的按鈕。 可以看到它的 `type` 被設置爲 `submit`,表明它是控制表單提交的按鈕。 在 `form` 中添加 `input` 元素,並像上個挑戰一樣設置其 `value` 和 `onChange()` 屬性。 然後,應該完成 `handleSubmit` 方法,以便將組件 state 屬性 `submit` 設置爲本地 `state` 下的當前輸入值。
|
||||
|
||||
**注意:** 還必須在提交處理程序中調用 `event.preventDefault()`,以防止將會刷新網頁的默認的表單提交行爲。 爲了便於學員操作,默認行爲在這裏被禁用,以防止重置挑戰的代碼。
|
||||
|
||||
最後,在 `form` 元素之後創建一個 `h1` 標籤,該標籤從組件的 `state` 渲染 `submit` 的值。 然後,可以在表單中鍵入任何內容,然後單擊按鈕(或按 enter 鍵),輸入會渲染到頁面上。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyForm` 應該返回一個包含 `form` 和 `h1` 標籤的 `div` 元素, 其中,表單中應該包括一個 `input` 和一個 `button`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyForm));
|
||||
return (
|
||||
mockedComponent.find('div').children().find('form').length === 1 &&
|
||||
mockedComponent.find('div').children().find('h1').length === 1 &&
|
||||
mockedComponent.find('form').children().find('input').length === 1 &&
|
||||
mockedComponent.find('form').children().find('button').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyForm` 的 state 應該用 `input` 和 `submit` 屬性初始化,且兩者都爲空字符串。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MyForm)).state('input') === '' &&
|
||||
Enzyme.mount(React.createElement(MyForm)).state('submit') === ''
|
||||
);
|
||||
```
|
||||
|
||||
`input` 元素中的輸入應該會更新組件中 state 的 `input` 屬性。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyForm));
|
||||
const _1 = () => {
|
||||
mockedComponent.setState({ input: '' });
|
||||
return mockedComponent.state('input');
|
||||
};
|
||||
const _2 = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: 'TestInput' } });
|
||||
return {
|
||||
state: mockedComponent.state('input'),
|
||||
inputVal: mockedComponent.find('input').props().value
|
||||
};
|
||||
};
|
||||
const before = _1();
|
||||
const after = _2();
|
||||
assert(
|
||||
before === '' &&
|
||||
after.state === 'TestInput' &&
|
||||
after.inputVal === 'TestInput'
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
提交表單應該運行 `handleSubmit`,它應該將 state 中的 `submit` 屬性設置爲當前輸入。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyForm));
|
||||
const _1 = () => {
|
||||
mockedComponent.setState({ input: '' });
|
||||
mockedComponent.setState({ submit: '' });
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: 'SubmitInput' } });
|
||||
return mockedComponent.state('submit');
|
||||
};
|
||||
const _2 = () => {
|
||||
mockedComponent.find('form').simulate('submit');
|
||||
return mockedComponent.state('submit');
|
||||
};
|
||||
const before = _1();
|
||||
const after = _2();
|
||||
assert(before === '' && after === 'SubmitInput');
|
||||
})();
|
||||
```
|
||||
|
||||
`handleSubmit` 應該調用 `event.preventDefault`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
__helpers.isCalledWithNoArgs(
|
||||
'event.preventDefault',
|
||||
MyForm.prototype.handleSubmit.toString()
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`h1` 標頭應該從組件的 state 渲染 `submit` 字段的值。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyForm));
|
||||
const _1 = () => {
|
||||
mockedComponent.setState({ input: '' });
|
||||
mockedComponent.setState({ submit: '' });
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: 'TestInput' } });
|
||||
return mockedComponent.find('h1').text();
|
||||
};
|
||||
const _2 = () => {
|
||||
mockedComponent.find('form').simulate('submit');
|
||||
return mockedComponent.find('h1').text();
|
||||
};
|
||||
const before = _1();
|
||||
const after = _2();
|
||||
assert(before === '' && after === 'TestInput');
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyForm />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
submit: ''
|
||||
};
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
handleSubmit(event) {
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{/* Change code below this line */}
|
||||
|
||||
{/* Change code above this line */}
|
||||
<button type='submit'>Submit!</button>
|
||||
</form>
|
||||
{/* Change code below this line */}
|
||||
|
||||
{/* Change code above this line */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
submit: ''
|
||||
};
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
this.setState(state => ({
|
||||
submit: state.input
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<input value={this.state.input} onChange={this.handleChange} />
|
||||
<button type='submit'>Submit!</button>
|
||||
</form>
|
||||
<h1>{this.state.submit}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,150 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036178
|
||||
title: 創建一個可以控制的輸入框
|
||||
challengeType: 6
|
||||
forumTopicId: 301385
|
||||
dashedName: create-a-controlled-input
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
應用程序可能在 `state` 和渲染的 UI 之間有更復雜的交互。 例如,用於文本輸入的表單控件元素(如 `input` 和 `textarea`)在用戶鍵入時在 DOM 中維護自己的 state。 通過 React,可以將這種可變 state 轉移到 React 組件的 `state` 中。 用戶的輸入變成了應用程序 `state` 的一部分,因此 React 控制該輸入字段的值。 通常,如果 React 組件具有用戶可以鍵入的輸入字段,那麼它將是一個受控的輸入表單。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器具有一個名爲 `ControlledInput` 的組件框架,用於創建受控的 `input` 元素。 組件的 `state` 已經被包含空字符串的 `input` 屬性初始化。 此值表示用戶在 `input` 字段中鍵入的文本。
|
||||
|
||||
首先,創建一個名爲 `handleChange()` 的方法,該方法具有一個名爲 `event` 的參數。 方法被調用時,它接收一個 `event` 對象,該對象包含一個來自 `input` 元素的字符串文本。 可以使用方法內的 `event.target.value` 來訪問這個字符串。 用這個新字符串更新組件的`state`的`input`屬性。
|
||||
|
||||
在 `render` 方法中的 `h4` 標籤之上創建 `input` 元素。 添加一個 `value` 屬性,使其等於組件 `state` 的 `input` 屬性。 然後將 `onChange()` 事件處理程序設置到 `handleChange()` 方法中。
|
||||
|
||||
在輸入框中鍵入時,文本由 `handleChange()` 方法處理,文本被設置爲本地 `state` 中的 `input` 屬性,並渲染在頁面上的 `input` 框中。 組件 `state` 是輸入數據的唯一真實來源。
|
||||
|
||||
最後,不要忘記在構造函數中添加必要的綁定。
|
||||
|
||||
# --hints--
|
||||
|
||||
`ControlledInput` 應該返回包含一個 `input` 標籤和 `p` 標籤的 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(ControlledInput))
|
||||
.find('div')
|
||||
.children()
|
||||
.find('input').length === 1 &&
|
||||
Enzyme.mount(React.createElement(ControlledInput))
|
||||
.find('div')
|
||||
.children()
|
||||
.find('p').length === 1
|
||||
);
|
||||
```
|
||||
|
||||
`ControlledInput` 的 state 應該使用設置爲空字符串的 `input` 屬性初始化。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(ControlledInput)).state('input'),
|
||||
''
|
||||
);
|
||||
```
|
||||
|
||||
Input 元素中的鍵入值應該更新 input 的 state 和值,並且 `p` 元素應該在輸入時呈現 state。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ControlledInput));
|
||||
const _1 = () => {
|
||||
mockedComponent.setState({ input: '' });
|
||||
return waitForIt(() => mockedComponent.state('input'));
|
||||
};
|
||||
const _2 = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: 'TestInput' } });
|
||||
return waitForIt(() => ({
|
||||
state: mockedComponent.state('input'),
|
||||
text: mockedComponent.find('p').text(),
|
||||
inputVal: mockedComponent.find('input').props().value
|
||||
}));
|
||||
};
|
||||
const before = await _1();
|
||||
const after = await _2();
|
||||
assert(
|
||||
before === '' &&
|
||||
after.state === 'TestInput' &&
|
||||
after.text === 'TestInput' &&
|
||||
after.inputVal === 'TestInput'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<ControlledInput />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class ControlledInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ''
|
||||
};
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
}
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */}
|
||||
|
||||
{ /* Change code above this line */}
|
||||
<h4>Controlled Input:</h4>
|
||||
<p>{this.state.input}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class ControlledInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ''
|
||||
};
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange} />
|
||||
<h4>Controlled Input:</h4>
|
||||
|
||||
<p>{this.state.input}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,102 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036163
|
||||
title: 創建一個 React 組件
|
||||
challengeType: 6
|
||||
forumTopicId: 301386
|
||||
dashedName: create-a-react-component
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
定義 React 組件的另一種方法是使用 ES6 的 `class`語法。 在以下示例中,`Kitten` 擴展了`React.Component`:
|
||||
|
||||
```jsx
|
||||
class Kitten extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<h1>Hi</h1>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
這將創建一個 ES6 類 `Kitten`,它擴展了 `React.Component` 類。 因此,`Kitten` 類現在可以訪問許多有用的 React 功能,例如本地狀態和生命週期鉤子。 如果還不熟悉這些術語,請不要擔心,在以後的挑戰中我們將更詳細地介紹它們。 另請注意,`Kitten` 類中定義了一個調用 `super()` 方法的 `constructor`。 它使用 `super()` 調用父類的構造函數,即本例中的 `React.Component`。 構造函數是使用 `class` 關鍵字創建的特殊方法,它在實例初始化之前調用。 最佳做法是在組件的 `constructor` 裏調用 `super`,並將 `props` 傳遞給它們, 這樣可以保證組件能夠正確地初始化。 目前爲止 ,需要知道這些代碼是必要的。 很快會了解到到構造函數的其他用途以及 `props`。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`MyComponent` 是使用類語法在代碼編輯器中定義的。 完成 `render` 方法的編寫,使其返回 `div` 元素,其中包含文本內容爲 `Hello React!` 的 `h1` 元素。
|
||||
|
||||
# --hints--
|
||||
|
||||
該 React 組件應該返回一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(Enzyme.shallow(React.createElement(MyComponent)).type() === 'div');
|
||||
```
|
||||
|
||||
返回的 `div` 中應該渲染一個 `h1` 標題。
|
||||
|
||||
```js
|
||||
assert(
|
||||
/<div><h1>.*<\/h1><\/div>/.test(
|
||||
Enzyme.shallow(React.createElement(MyComponent)).html()
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`h1` 標題中應該包含字符串 `Hello React!`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.shallow(React.createElement(MyComponent)).html() ===
|
||||
'<div><h1>Hello React!</h1></div>'
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
// Change code below this line
|
||||
|
||||
|
||||
|
||||
// Change code above this line
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
// Change code below this line
|
||||
return (
|
||||
<div>
|
||||
<h1>Hello React!</h1>
|
||||
</div>
|
||||
);
|
||||
// Change code above this line
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,57 @@
|
||||
---
|
||||
id: 587d7dbc367417b2b2512bb1
|
||||
title: 創建一個簡單的 JSX 元素
|
||||
challengeType: 6
|
||||
forumTopicId: 301390
|
||||
dashedName: create-a-simple-jsx-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
簡介:React 是由 Facebook 創建和維護的開源視圖庫。 它是渲染現代 Web 應用程序用戶界面(UI)的好工具。
|
||||
|
||||
React 使用名爲 JSX 的 JavaScript 語法擴展,可以直接在 JavaScript 中編寫 HTML。 這有幾個好處。 可以在 HTML 中使用 JavaScript 的完整程序功能,並有助於保持代碼的可讀性。 在大多數情況下,JSX 類似於已經學過的 HTML,但是在這些挑戰中將會涉及一些關鍵差異。
|
||||
|
||||
例如,因爲 JSX 是 JavaScript 的語法擴展,所以實際上可以直接在 JSX 中編寫 JavaScript。 要做到這一點,只需在花括號中包含希望被視爲 JavaScript 的代碼:`{ 'this is treated as JavaScript code' }`(這被視爲 JavaScript 代碼)。 請牢記這個寫法,將會在接下來的挑戰中使用。
|
||||
|
||||
但是,由於瀏覽器不能解析 JSX,因此必須將 JSX 代碼編譯爲 JavaScript。 在這個過程中,轉換器 Babel 是一個很受歡迎的工具。 後續挑戰已經在後臺引入了 Babel,可以直接寫 JSX 代碼。 如果代碼不符合 JSX 語法,那麼挑戰中的第一個測試就不會通過。
|
||||
|
||||
值得注意的是,這些挑戰在底層調用 `ReactDOM.render(JSX, document.getElementById('root'))`。 這個函數調用將 JSX 置於 React 自己的輕量級 DOM 中。 然後,React 使用自己的 DOM 快照來實現增量更新。
|
||||
|
||||
# --instructions--
|
||||
|
||||
當前代碼使用 JSX 將 `div` 元素賦值給常量 `JSX`。 將 `div` 替換爲 `h1` 元素,並在其中添加文本 `Hello JSX!`。
|
||||
|
||||
# --hints--
|
||||
|
||||
常量 `JSX` 應該返回一個 `h1` 元素。
|
||||
|
||||
```js
|
||||
assert(JSX.type === 'h1');
|
||||
```
|
||||
|
||||
`h1` 標籤應該包含文本 `Hello JSX!`。
|
||||
|
||||
```js
|
||||
assert(Enzyme.shallow(JSX).contains('Hello JSX!'));
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(JSX, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const JSX = <div></div>;
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const JSX = <h1>Hello JSX!</h1>;
|
||||
```
|
@ -0,0 +1,134 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036170
|
||||
title: 創建一個有狀態的組件
|
||||
challengeType: 6
|
||||
forumTopicId: 301391
|
||||
dashedName: create-a-stateful-component
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
React 中最重要的主題之一是 `state`。 state 包含應用程序需要了解的任何數據,這些數據可能會隨時間而變化。 應用程序能夠響應 state 的變更,並在必要時顯示更新後的 UI。 React 爲現代 Web 應用程序的狀態管理提供了一個很好的解決方案。
|
||||
|
||||
可以在類組件的 `constructor` 上聲明 `state` 屬性來在 React 組件中創建 state, 它在創建時使用 `state` 初始化組件。 `state` 屬性必須設置爲 JavaScript `object`(對象)。 聲明如下:
|
||||
|
||||
```jsx
|
||||
this.state = {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
可以在組件的整個生命週期內訪問 `state` 對象, 可以更新它、在 UI 中渲染它,也可以將其作爲 props 傳遞給子組件。 `state` 對象的使用可以很簡單,亦可以很複雜,就看你怎麼用了。 請注意,必須通過擴展 `React.Component` 來創建類組件,以便像這樣創建 `state`。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器中有一個組件試圖從其 `state` 中渲染一個 `name` 屬性, 但是 `state` 還沒有定義。 在 `constructor` 中使用 `state` 初始化組件,並將你的名字賦給 `name` 屬性。
|
||||
|
||||
# --hints--
|
||||
|
||||
`StatefulComponent` 應該存在並被渲染。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(
|
||||
React.createElement(StatefulComponent)
|
||||
);
|
||||
return mockedComponent.find('StatefulComponent').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`StatefulComponent` 應該渲染一個 `div` 元素和一個 `h1` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(
|
||||
React.createElement(StatefulComponent)
|
||||
);
|
||||
return (
|
||||
mockedComponent.find('div').length === 1 &&
|
||||
mockedComponent.find('h1').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應使用被設置爲字符串的 `name` 屬性來初始化 `StatefulComponent` 的 state。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(
|
||||
React.createElement(StatefulComponent)
|
||||
);
|
||||
const initialState = mockedComponent.state();
|
||||
return (
|
||||
typeof initialState === 'object' && typeof initialState.name === 'string'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`StatefulComponent` 中 state 的 `name` 屬性應該渲染在 `h1` 元素裏。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(
|
||||
React.createElement(StatefulComponent)
|
||||
);
|
||||
const initialState = mockedComponent.state();
|
||||
return mockedComponent.find('h1').text() === initialState.name;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<StatefulComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class StatefulComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// Only change code below this line
|
||||
|
||||
// Only change code above this line
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>{this.state.name}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class StatefulComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: 'freeCodeCamp!'
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>{this.state.name}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,102 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036162
|
||||
title: 創建一個無狀態的函數組件
|
||||
challengeType: 6
|
||||
forumTopicId: 301392
|
||||
dashedName: create-a-stateless-functional-component
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
組件是 React 的核心。 React 中的所有內容都是一個組件,在這裏將學習如何創建一個組件。
|
||||
|
||||
有兩種方法可以創建 React 組件。 第一種方法是使用 JavaScript 函數。 以這種方式定義組件會創建*無狀態功能組件*。 應用程序中的狀態概念將在以後的挑戰中介紹。 目前爲止,可以將無狀態組件視爲能接收數據並對其進行渲染,但不管理或跟蹤該數據的更改的組件。 (我們將下一個挑戰使用中第二種方式創建 React 組件。)
|
||||
|
||||
要用函數創建組件,只需編寫一個返回 JSX 或 `null` 的 JavaScript 函數。 需要注意的一點是,React 要求你的函數名以大寫字母開頭。 下面是一個無狀態功能組件的示例,該組件在 JSX 中分配一個 HTML 的 class:
|
||||
|
||||
```jsx
|
||||
const DemoComponent = function() {
|
||||
return (
|
||||
<div className='customClass' />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
翻譯完成後, `<div>` 將有一個 `customClass` 的 CSS class。
|
||||
|
||||
因爲 JSX 組件代表 HTML,所以你可以將幾個組件放在一起以創建更復雜的 HTML 頁面。 這是 React 提供的組件架構的關鍵優勢之一。 它允許用許多獨立的組件組合成 UI。 這使得構建和維護複雜的用戶界面變得更加容易。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器中有一個名爲 `MyComponent` 的函數。 完成此函數,使其返回包含一些文本字符串的單個`div`元素。
|
||||
|
||||
**注意:** 文本被視爲是 `div` 的子元素,因此不能使用自閉合標籤。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該返回 JSX。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return mockedComponent.length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 應該返回一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return mockedComponent.children().type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`div` 元素應該包含一個文本字符串。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return mockedComponent.find('div').text() !== '';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const MyComponent = function() {
|
||||
// Change code below this line
|
||||
|
||||
|
||||
|
||||
// Change code above this line
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const MyComponent = function() {
|
||||
// Change code below this line
|
||||
return (
|
||||
<div>
|
||||
Demo Solution
|
||||
</div>
|
||||
);
|
||||
// Change code above this line
|
||||
}
|
||||
```
|
@ -0,0 +1,62 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036160
|
||||
title: 在 JSX 中定義一個 HTML Class
|
||||
challengeType: 6
|
||||
forumTopicId: 301393
|
||||
dashedName: define-an-html-class-in-jsx
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
現在已經習慣了編寫 JSX,可能想知道它與 HTML 有什麼不同。
|
||||
|
||||
到目前爲止,HTML 和 JSX 似乎完全相同。
|
||||
|
||||
JSX 的一個關鍵區別是你不能再使用 `class` 這個單詞來做爲 HTML 的 class 名。 這是因爲 `class` 是 JavaScript 中的關鍵字。 而 JSX 使用 `className` 來代替。
|
||||
|
||||
事實上,JSX 中所有 HTML 屬性和事件引用的命名約定都變成了駝峯式。 例如,JSX 中的單擊事件是 `onClick`,而不是 `onclick`。 同樣,`onchange` 變成了`onChange`。 雖然這是一個微小的差異,但請你一定要記住。
|
||||
|
||||
# --instructions--
|
||||
|
||||
將 class `myDiv` 應用於 JSX 提供的 `div`上。
|
||||
|
||||
# --hints--
|
||||
|
||||
常量`JSX`應該返回一個`div`元素。
|
||||
|
||||
```js
|
||||
assert.strictEqual(JSX.type, 'div');
|
||||
```
|
||||
|
||||
`div` 應該有一個 `myDiv` class。
|
||||
|
||||
```js
|
||||
assert.strictEqual(JSX.props.className, 'myDiv');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(JSX, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const JSX = (
|
||||
<div>
|
||||
<h1>Add a class to this div</h1>
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const JSX = (
|
||||
<div className = 'myDiv'>
|
||||
<h1>Add a class to this div</h1>
|
||||
</div>);
|
||||
```
|
@ -0,0 +1,147 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403618b
|
||||
title: 給同級元素一個唯一的鍵屬性
|
||||
challengeType: 6
|
||||
forumTopicId: 301394
|
||||
dashedName: give-sibling-elements-a-unique-key-attribute
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
上一個挑戰展示瞭如何使用 `map` 方法根據用戶輸入動態渲染多個元素。 然而,這個例子中缺少一個重要的部分。 創建元素數組時,每個元素都需要一個設置爲唯一值的 `key` 屬性。 React 使用這些鍵來跟蹤哪些項目被添加、更改或刪除。 這有助於在以任何方式修改列表時提高重新渲染過程的效率。
|
||||
|
||||
**注意:** 鍵只需要在兄弟元素之間是唯一的,它們不需要在應用程序中是全局唯一的。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器有一個數組,它包含一些前端框架和一個名爲 `Frameworks()` 的無狀態函數組件。 `Frameworks()` 需要將數組映射到無序列表,就像上一個挑戰一樣。 完成 `map` 回調,爲 `frontEndFrameworks` 數組中的每個框架返回一個 `li` 元素。 這次,確保給每個 `li` 的 `key` 屬性設置一個唯一的值。 `li` 元素還應該包含來自 `frontEndFrameworks` 的文本。
|
||||
|
||||
通常,希望使 key 能唯一標識要渲染的元素。 數組索引可以是最後的選擇,但通常你應該嘗試使用唯一標識。
|
||||
|
||||
# --hints--
|
||||
|
||||
`Frameworks` 組件應該存在並渲染到頁面。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(Frameworks)).find('Frameworks').length === 1
|
||||
);
|
||||
```
|
||||
|
||||
`Frameworks` 應該渲染一個 `h1` 元素。
|
||||
|
||||
```js
|
||||
assert(Enzyme.mount(React.createElement(Frameworks)).find('h1').length === 1);
|
||||
```
|
||||
|
||||
`Frameworks` 應該渲染一個 `ul` 元素。
|
||||
|
||||
```js
|
||||
assert(Enzyme.mount(React.createElement(Frameworks)).find('ul').length === 1);
|
||||
```
|
||||
|
||||
`ul` 標籤應該渲染 6 個子 `li` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(Frameworks)).find('ul').children().length ===
|
||||
6 &&
|
||||
Enzyme.mount(React.createElement(Frameworks))
|
||||
.find('ul')
|
||||
.childAt(0)
|
||||
.name() === 'li' &&
|
||||
Enzyme.mount(React.createElement(Frameworks)).find('li').length === 6
|
||||
);
|
||||
```
|
||||
|
||||
每個列表項元素應該具有唯一的 `key` 屬性。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const ul = Enzyme.mount(React.createElement(Frameworks)).find('ul');
|
||||
const keys = new Set([
|
||||
ul.childAt(0).key(),
|
||||
ul.childAt(1).key(),
|
||||
ul.childAt(2).key(),
|
||||
ul.childAt(3).key(),
|
||||
ul.childAt(4).key(),
|
||||
ul.childAt(5).key()
|
||||
]);
|
||||
return keys.size === 6;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
每個列表項元素應該包含來自 `frontEndFrameworks` 的文本。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const li = Enzyme.mount(React.createElement(Frameworks))
|
||||
.find('ul')
|
||||
.children();
|
||||
return [...Array(5)].every((_, i) =>
|
||||
frontEndFrameworks.includes(li.at(i).text())
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<Frameworks />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const frontEndFrameworks = [
|
||||
'React',
|
||||
'Angular',
|
||||
'Ember',
|
||||
'Knockout',
|
||||
'Backbone',
|
||||
'Vue'
|
||||
];
|
||||
|
||||
function Frameworks() {
|
||||
const renderFrameworks = null; // Change this line
|
||||
return (
|
||||
<div>
|
||||
<h1>Popular Front End JavaScript Frameworks</h1>
|
||||
<ul>
|
||||
{renderFrameworks}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const frontEndFrameworks = [
|
||||
'React',
|
||||
'Angular',
|
||||
'Ember',
|
||||
'Knockout',
|
||||
'Backbone',
|
||||
'Vue'
|
||||
];
|
||||
|
||||
function Frameworks() {
|
||||
const renderFrameworks = frontEndFrameworks.map((fw, i) => <li key={i}>{fw}</li>);
|
||||
return (
|
||||
<div>
|
||||
<h1>Popular Front End JavaScript Frameworks</h1>
|
||||
<ul>
|
||||
{renderFrameworks}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
@ -0,0 +1,104 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036181
|
||||
title: 介紹內聯樣式
|
||||
challengeType: 6
|
||||
forumTopicId: 301395
|
||||
dashedName: introducing-inline-styles
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
還有其他複雜的概念可以爲 React 代碼增加強大的功能。 但是,你可能會想知道更簡單的問題,比如:如何對在 React 中創建的 JSX 元素添加樣式。 你可能知道,由於[將 class 應用於 JSX 元素的方式](/learn/front-end-development-libraries/react/define-an-html-class-in-jsx)與 HTML 中的使用並不完全相同。
|
||||
|
||||
如果從樣式表導入樣式,它就沒有太大的不同。 使用 `className` 屬性將 class 應用於 JSX 元素,並將樣式應用於樣式表中的 class。 另一種選擇是使用內聯樣式,這在 ReactJS 開發中非常常見。
|
||||
|
||||
將內聯樣式應用於 JSX 元素,類似於在 HTML 中的操作方式,但有一些 JSX 差異。 以下是 HTML 中內聯樣式的示例:
|
||||
|
||||
```jsx
|
||||
<div style="color: yellow; font-size: 16px">Mellow Yellow</div>
|
||||
```
|
||||
|
||||
JSX 元素使用 `style` 屬性,但是鑑於 JSX 的編譯方式,不能將值設置爲 `string`(字符串)。 相反,你應該將其設置爲等於JavaScript `object` 。 如下所示:
|
||||
|
||||
```jsx
|
||||
<div style={{color: "yellow", fontSize: 16}}>Mellow Yellow</div>
|
||||
```
|
||||
|
||||
注意到如何駝峯拼寫 `fontSize` 屬性了嗎? 這是因爲 React 不接受樣式對象中的 kebab-case 鍵。 React 將在 HTML 中爲應用正確的屬性名稱。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在代碼編輯器中給 `div` 添加一個 `style` 屬性,將文本顏色設置爲紅色,字體大小設置爲 `72px`。
|
||||
|
||||
請注意,可以選擇將字體大小設置爲數字,省略單位 `px`,或者將其寫爲 `72px`。
|
||||
|
||||
# --hints--
|
||||
|
||||
組件應該渲染一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Colorful));
|
||||
return mockedComponent.children().type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`div` 元素的顏色應該是 `red`(紅色)。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Colorful));
|
||||
return mockedComponent.children().props().style.color === 'red';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`div` 元素的字體大小應爲 `72px`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Colorful));
|
||||
return (
|
||||
mockedComponent.children().props().style.fontSize === 72 ||
|
||||
mockedComponent.children().props().style.fontSize === '72' ||
|
||||
mockedComponent.children().props().style.fontSize === '72px'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<Colorful />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class Colorful extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>Big Red</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class Colorful extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div style={{color: "red", fontSize: 72}}>Big Red</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,75 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036161
|
||||
title: 瞭解 JSX 的自動閉合
|
||||
challengeType: 6
|
||||
forumTopicId: 301396
|
||||
dashedName: learn-about-self-closing-jsx-tags
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
到目前爲止,已經看到 JSX 與 HTML 的關鍵不同在於使用 `className` 還是 `class` 來定義 HTML 的 class。
|
||||
|
||||
JSX 不同於 HTML 的另一個重要方面是自閉合標籤。
|
||||
|
||||
在HTML中,幾乎所有的標籤都有一個開始和結束標籤:`<div></div>`,結束標籤在你要關閉的標籤名之前始終具有正斜槓。 但是,HTML 中有一些稱爲 “自閉合標籤” 的特殊實例,它們在另一個標籤開始之前,不需要開始和結束標籤都存在。
|
||||
|
||||
例如,換行標籤可以寫成 `<br>` 或者 `<br />`,但是不應該寫成 `<br></br>`,因爲它不包含任何內容。
|
||||
|
||||
在 JSX 中,規則略有不同。 任何 JSX 元素都可以使用自閉合標籤編寫,並且每個元素都必須關閉。 例如,爲了通過編譯換行標籤必須始終編寫爲 `<br />`。 另一方面 `<div>` 可以寫成 `<div />` 或者 `<div></div>`。 不同之處在於,在第一個語法版本中,無法在 `<div />` 中包含任何內容。 在後面的挑戰中你會發現,這種語法在渲染 React 組件時非常有用。
|
||||
|
||||
# --instructions--
|
||||
|
||||
修復代碼編輯器中的錯誤,使其成爲有效的 JSX 併成功編譯。 確保不更改任何內容 -- 只需要在需要的地方關閉標籤。
|
||||
|
||||
# --hints--
|
||||
|
||||
常量 `JSX` 應該返回一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert.strictEqual(JSX.type, 'div');
|
||||
```
|
||||
|
||||
`div` 應該包含一個 `br` 標籤。
|
||||
|
||||
```js
|
||||
assert(Enzyme.shallow(JSX).find('br').length === 1);
|
||||
```
|
||||
|
||||
`div` 應該包含一個 `hr` 標籤。
|
||||
|
||||
```js
|
||||
assert(Enzyme.shallow(JSX).find('hr').length === 1);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(JSX, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const JSX = (
|
||||
<div>
|
||||
<h2>Welcome to React!</h2> <br >
|
||||
<p>Be sure to close all tags!</p>
|
||||
<hr >
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const JSX = (
|
||||
<div>
|
||||
<h2>Welcome to React!</h2> <br />
|
||||
<p>Be sure to close all tags!</p>
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
```
|
@ -0,0 +1,187 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036180
|
||||
title: 使用 shouldComponentUpdate 優化重新渲染
|
||||
challengeType: 6
|
||||
forumTopicId: 301398
|
||||
dashedName: optimize-re-renders-with-shouldcomponentupdate
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
到目前爲止,如果任何組件接收到新的 `state` 或新的 `props`,它會重新渲染自己及其所有子組件。 這通常是好的。 但是 React 提供了一種生命週期方法,當子組件接收到新的 `state` 或 `props` 時,可以調用該方法,並特別聲明組件是否應該更新。 這個方法就是 `shouldComponentUpdate()`,它將 `nextProps` 和 `nextState` 作爲參數。
|
||||
|
||||
這種方法是優化性能的有效方法。 例如,默認行爲是,當組件接收到新的 `props` 時,即使 `props` 沒有改變,它也會重新渲染。 可以通過使用 `shouldComponentUpdate()` 比較 `props` 來防止這種情況發生。 該方法必須返回一個 `boolean`(布爾值),該值告訴 React 是否更新組件。 可以比較當前的 props(`this.props`)和下一個 props(`nextProps`),以確定你是否需要更新,並相應地返回 `true` 或 `false`。
|
||||
|
||||
# --instructions--
|
||||
|
||||
將 `shouldComponentUpdate()` 方法添加到名爲 `OnlyEvens` 的組件中。 目前,該方法返回 `true`,因此每次收到新的 `props` 時,`OnlyEvens` 都會重新渲染。 修改該方法,以便 `OnlyEvens` 僅在其新 props 的 `value` 爲偶數時更新。 單擊 `Add` 按鈕,在觸發其他生命週期鉤子時,在瀏覽器控制檯中查看事件的順序。
|
||||
|
||||
# --hints--
|
||||
|
||||
`Controller` 組件應該將 `OnlyEvens` 組件渲染爲子組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Controller));
|
||||
return (
|
||||
mockedComponent.find('Controller').length === 1 &&
|
||||
mockedComponent.find('OnlyEvens').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應該在 `OnlyEvens` 組件上定義 `shouldComponentUpdate` 方法。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const child = React.createElement(OnlyEvens)
|
||||
.type.prototype.shouldComponentUpdate.toString()
|
||||
.replace(/s/g, '');
|
||||
return child !== 'undefined';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`OnlyEvens` 組件應該返回一個 `h1` 標籤,該標籤渲染 `this.props.value` 的值。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Controller));
|
||||
const first = () => {
|
||||
mockedComponent.setState({ value: 1000 });
|
||||
return mockedComponent.find('h1').html();
|
||||
};
|
||||
const second = () => {
|
||||
mockedComponent.setState({ value: 10 });
|
||||
return mockedComponent.find('h1').html();
|
||||
};
|
||||
const firstValue = first();
|
||||
const secondValue = second();
|
||||
assert(firstValue === '<h1>1000</h1>' && secondValue === '<h1>10</h1>');
|
||||
})();
|
||||
```
|
||||
|
||||
只有在 `nextProps.value` 爲偶數時,`OnlyEvens` 纔會重新渲染。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Controller));
|
||||
const first = () => {
|
||||
mockedComponent.setState({ value: 8 });
|
||||
return mockedComponent.find('h1').text();
|
||||
};
|
||||
const second = () => {
|
||||
mockedComponent.setState({ value: 7 });
|
||||
return mockedComponent.find('h1').text();
|
||||
};
|
||||
const third = () => {
|
||||
mockedComponent.setState({ value: 42 });
|
||||
return mockedComponent.find('h1').text();
|
||||
};
|
||||
const firstValue = first();
|
||||
const secondValue = second();
|
||||
const thirdValue = third();
|
||||
assert(firstValue === '8' && secondValue === '8' && thirdValue === '42');
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<Controller />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class OnlyEvens extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
console.log('Should I update?');
|
||||
// Change code below this line
|
||||
return true;
|
||||
// Change code above this line
|
||||
}
|
||||
componentDidUpdate() {
|
||||
console.log('Component re-rendered.');
|
||||
}
|
||||
render() {
|
||||
return <h1>{this.props.value}</h1>;
|
||||
}
|
||||
}
|
||||
|
||||
class Controller extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: 0
|
||||
};
|
||||
this.addValue = this.addValue.bind(this);
|
||||
}
|
||||
addValue() {
|
||||
this.setState(state => ({
|
||||
value: state.value + 1
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.addValue}>Add</button>
|
||||
<OnlyEvens value={this.state.value} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class OnlyEvens extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
console.log('Should I update?');
|
||||
// Change code below this line
|
||||
return nextProps.value % 2 === 0;
|
||||
// Change code above this line
|
||||
}
|
||||
componentDidUpdate() {
|
||||
console.log('Component re-rendered.');
|
||||
}
|
||||
render() {
|
||||
return <h1>{this.props.value}</h1>;
|
||||
}
|
||||
}
|
||||
|
||||
class Controller extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: 0
|
||||
};
|
||||
this.addValue = this.addValue.bind(this);
|
||||
}
|
||||
addValue() {
|
||||
this.setState(state => ({
|
||||
value: state.value + 1
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.addValue}>Add</button>
|
||||
<OnlyEvens value={this.state.value} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,112 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403616c
|
||||
title: 覆蓋默認的 Props
|
||||
challengeType: 6
|
||||
forumTopicId: 301399
|
||||
dashedName: override-default-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
在 React 中,設置默認的 props 是一個很有用的特性, 顯式設置組件的 prop 值即可覆蓋默認 props。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`ShoppingCart` 組件現在渲染了一個子組件 `Items`。 該 `Items` 組件有一個默認 `quantity` prop,其值被設置爲整數 `0`。 通過傳入數值 `10` 來覆蓋 `quantity` 的默認 prop。
|
||||
|
||||
**注意:** 請記住,向組件添加 prop 的語法與添加 HTML 屬性類似。 但是,由於 `quantity` 的值是整數,所以它不會加引號,但應該用花括號括起來, 例如`{100}`。 這個語法告訴 JSX 直接將花括號中的值解釋爲 JavaScript。
|
||||
|
||||
# --hints--
|
||||
|
||||
應該渲染 `ShoppingCart` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart));
|
||||
return mockedComponent.find('ShoppingCart').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應該渲染 `Items` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart));
|
||||
return mockedComponent.find('Items').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Items` 組件應該有一個 `{ quantity: 10 }` 的 prop,該 prop 是從 `ShoppingCart` 組件傳遞過去的。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart));
|
||||
return (
|
||||
mockedComponent.find('Items').props().quantity == 10 &&
|
||||
getUserInput('index')
|
||||
.replace(/ /g, '')
|
||||
.includes('<Itemsquantity={10}/>')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<ShoppingCart />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const Items = (props) => {
|
||||
return <h1>Current Quantity of Items in Cart: {props.quantity}</h1>
|
||||
}
|
||||
|
||||
Items.defaultProps = {
|
||||
quantity: 0
|
||||
}
|
||||
|
||||
class ShoppingCart extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
{ /* Change code below this line */ }
|
||||
return <Items />
|
||||
{ /* Change code above this line */ }
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const Items = (props) => {
|
||||
return <h1>Current Quantity of Items in Cart: {props.quantity}</h1>
|
||||
}
|
||||
|
||||
Items.defaultProps = {
|
||||
quantity: 0
|
||||
}
|
||||
|
||||
class ShoppingCart extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
{ /* Change code below this line */ }
|
||||
return <Items quantity = {10} />
|
||||
{ /* Change code above this line */ }
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,217 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403617b
|
||||
title: 傳遞迴調作爲 Props
|
||||
challengeType: 6
|
||||
forumTopicId: 301400
|
||||
dashedName: pass-a-callback-as-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
可以將 `state` 作爲 props 傳遞給子組件,但不僅限於傳遞數據。 也可以將函數或在 React 組件中定義的任何方法傳遞給子組件。 這就是子組件與父組件交互的方式。 可以把方法像普通 prop 一樣傳遞給子組件, 它會被分配一個名字,可以在子組件中的 `this.props` 下訪問該方法的名字。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器中列出了三個組件。 `MyApp` 是父組件,`GetInput` 和`RenderInput` 是它將要渲染的子組件。 將 `GetInput` 組件添加到 `MyApp` 的 render 方法,然後將 `MyApp` 的 `state` 中的 `inputValue` 傳入名爲 `input` 的 prop。 還要創建一個名爲 `handleChange` 的 prop,並將輸入處理程序 `handleChange` 傳遞給它。
|
||||
|
||||
接下來,將 `RenderInput` 添加到 `MyApp` 中的 render 方法中,然後創建一個名爲 `input` 的 prop,並將 `state` 中的 `inputValue` 傳遞給它。 完成後,可以在 `GetInput` 組件中的 `input` 字段中鍵入內容,然後該組件通過 props 調用其父組件中的處理函數方法。 這將更新處於父組件 `state` 中的 input,該 input 將作爲 props 傳遞給兩個子組件。 觀察數據如何在組件之間流動,以及單一數據源如何保持父組件`state`。 誠然,這個示例有點做作,但是應該能用來說明數據和回調是如何在 React 組件之間傳遞的。
|
||||
|
||||
# --hints--
|
||||
|
||||
應該渲染 `MyApp` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
return mockedComponent.find('MyApp').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應該渲染 `GetInput` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
return mockedComponent.find('GetInput').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應該渲染 `RenderInput` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
return mockedComponent.find('RenderInput').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`GetInput` 組件應該接收 `MyApp` 的 state 屬性 `inputValue` 作爲 props,幷包含一個修改 `MyApp` state 的 `input` 元素。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
const state_1 = () => {
|
||||
mockedComponent.setState({ inputValue: '' });
|
||||
return waitForIt(() => mockedComponent.state());
|
||||
};
|
||||
const state_2 = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: 'TestInput' } });
|
||||
return waitForIt(() => mockedComponent.state());
|
||||
};
|
||||
const updated_1 = await state_1();
|
||||
const updated_2 = await state_2();
|
||||
assert(updated_1.inputValue === '' && updated_2.inputValue === 'TestInput');
|
||||
};
|
||||
```
|
||||
|
||||
`RenderInput` 組件應該接收 `MyApp` state 屬性 `inputValue` 作爲 props。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
const state_1 = () => {
|
||||
mockedComponent.setState({ inputValue: 'TestName' });
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated_1 = await state_1();
|
||||
assert(updated_1.find('p').text().includes('TestName'));
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyApp />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inputValue: ''
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
inputValue: event.target.value
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class GetInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>Get Input:</h3>
|
||||
<input
|
||||
value={this.props.input}
|
||||
onChange={this.props.handleChange}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class RenderInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>Input Render:</h3>
|
||||
<p>{this.props.input}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inputValue: ''
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
inputValue: event.target.value
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<GetInput
|
||||
input={this.state.inputValue}
|
||||
handleChange={this.handleChange}/>
|
||||
<RenderInput
|
||||
input={this.state.inputValue}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class GetInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>Get Input:</h3>
|
||||
<input
|
||||
value={this.props.input}
|
||||
onChange={this.props.handleChange}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class RenderInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>Input Render:</h3>
|
||||
<p>{this.props.input}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,186 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403616a
|
||||
title: 傳遞一個數組作爲 Props
|
||||
challengeType: 6
|
||||
forumTopicId: 301401
|
||||
dashedName: pass-an-array-as-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
上一個挑戰演示瞭如何將來自父組件的信息作爲 `props` 傳遞給子組件。 這個挑戰着眼於如何將數組作爲 `props` 傳遞。 要將數組傳遞給 JSX 元素,必須將其視爲 JavaScript 並用花括號括起來。
|
||||
|
||||
```jsx
|
||||
<ParentComponent>
|
||||
<ChildComponent colors={["green", "blue", "red"]} />
|
||||
</ParentComponent>
|
||||
```
|
||||
|
||||
這樣,子組件就可以訪問數組屬性 `colors`。 訪問屬性時可以使用 `join()` 等數組方法。 `const ChildComponent = (props) => <p>{props.colors.join(', ')}</p>` 這將把所有 `colors` 數組項連接成一個逗號分隔的字符串並生成: `<p>green, blue, red</p>` 稍後,我們將瞭解在 React 中渲染數組數據的其他常用方法。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器中有 `List` 和 `ToDo` 組件。 在 `ToDo` 組件中渲染每個 `List` 時,傳入 `tasks` 屬性並將其分配給待辦任務數組,例如 `["walk dog", "workout"]`。 然後訪問 `List` 組件中的 `tasks` 數組,在`p`元素中顯示其值。 使用 `join(", ")` 把 `props.tasks` 數組作爲逗號分隔列表顯示在 `p` 元素中。 今天的列表應該至少有 2 個任務,明天的列表應該至少有 3 個任務。
|
||||
|
||||
# --hints--
|
||||
|
||||
`ToDo` 組件應該返回單個外部 `div`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return mockedComponent.children().first().type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`ToDo` 組件的第三個子元素應該是 `List` 組件的一個實例。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return mockedComponent.children().first().childAt(2).name() === 'List';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`ToDo` 組件的第五個子元素應該是 `List` 組件的一個實例。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return mockedComponent.children().first().childAt(4).name() === 'List';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`List` 組件的兩個實例都應該具有一個名爲 `tasks` 的屬性,並且 `tasks` 的類型應該是數組。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return (
|
||||
Array.isArray(mockedComponent.find('List').get(0).props.tasks) &&
|
||||
Array.isArray(mockedComponent.find('List').get(1).props.tasks)
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
表示今天任務的第一個 `List` 組件應該有 2 個或更多項。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return mockedComponent.find('List').get(0).props.tasks.length >= 2;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
表示明天任務的第二個 `List` 組件應該有 3 個或更多項。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return mockedComponent.find('List').get(1).props.tasks.length >= 3;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`List` 組件應在 `p` 標籤中渲染 `tasks` 屬性的值。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return (
|
||||
mockedComponent
|
||||
.find('p')
|
||||
.get(0)
|
||||
.props.children.replace(/\s*,\s*/g, ',') ===
|
||||
mockedComponent
|
||||
.find('List')
|
||||
.get(0)
|
||||
.props.tasks.join(',')
|
||||
.replace(/\s*,\s*/g, ',') &&
|
||||
mockedComponent
|
||||
.find('p')
|
||||
.get(1)
|
||||
.props.children.replace(/\s*,\s*/g, ',') ===
|
||||
mockedComponent
|
||||
.find('List')
|
||||
.get(1)
|
||||
.props.tasks.join(',')
|
||||
.replace(/\s*,\s*/g, ',')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<ToDo />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const List = (props) => {
|
||||
{ /* Change code below this line */ }
|
||||
return <p>{}</p>
|
||||
{ /* Change code above this line */ }
|
||||
};
|
||||
|
||||
class ToDo extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>To Do Lists</h1>
|
||||
<h2>Today</h2>
|
||||
{ /* Change code below this line */ }
|
||||
<List/>
|
||||
<h2>Tomorrow</h2>
|
||||
<List/>
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const List= (props) => {
|
||||
return <p>{props.tasks.join(', ')}</p>
|
||||
};
|
||||
|
||||
class ToDo extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>To Do Lists</h1>
|
||||
<h2>Today</h2>
|
||||
<List tasks={['study', 'exercise']} />
|
||||
<h2>Tomorrow</h2>
|
||||
<List tasks={['call Sam', 'grocery shopping', 'order tickets']} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,164 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036169
|
||||
title: 將 Props 傳遞給無狀態函數組件
|
||||
challengeType: 6
|
||||
forumTopicId: 301402
|
||||
dashedName: pass-props-to-a-stateless-functional-component
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
之前的挑戰涵蓋了關於在 React 中創建和組合 JSX 元素、函數組件和 ES6 風格的類組件的很多內容。 有了這個基礎,現在是時候看看 React 中的另一個常見特性 **props** 了。 在 React 中,可以將屬性傳遞給子組件。 假設有一個 `App` 組件,該組件渲染了一個名爲 `Welcome` 的子組件,它是一個無狀態函數組件。 可以通過以下方式給 `Welcome` 傳遞一個 `user` 屬性:
|
||||
|
||||
```jsx
|
||||
<App>
|
||||
<Welcome user='Mark' />
|
||||
</App>
|
||||
```
|
||||
|
||||
可以把創建的 React 支持的**自定義 HTML 屬性**傳遞給組件, 在上面的例子裏,將創建的屬性 `user` 傳遞給組件 `Welcome`。 由於 `Welcome` 是一個無狀態函數組件,它可以像這樣訪問該值:
|
||||
|
||||
```jsx
|
||||
const Welcome = (props) => <h1>Hello, {props.user}!</h1>
|
||||
```
|
||||
|
||||
調用 `props` 這個值是常見做法,當處理無狀態函數組件時,基本上可以將其視爲返回 JSX 的函數的參數。 這樣,你就可以在函數體中訪問該值。 但對於類組件,訪問方式會略有不同。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器中有 `Calendar` 和 `CurrentDate` 組件。 從 `Calendar` 組件渲染 `CurrentDate` 時,從 JavaScript 的 `Date` 對象分配當前日期,並將其作爲 `date` 屬性傳入。 然後訪問 `CurrentDate` 組件的 `prop`,並在 `p` 標籤中顯示其值。 請注意,要將 `prop` 的值視爲 JavaScript,必須將它們括在花括號中,例如`date={Date()}`。
|
||||
|
||||
# --hints--
|
||||
|
||||
`Calendar` 組件應該返回單個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Calendar));
|
||||
return mockedComponent.children().type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Calendar` 組件的第二個子元素應該是 `CurrentDate` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Calendar));
|
||||
return mockedComponent.children().childAt(1).name() === 'CurrentDate';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`CurrentDate` 組件應該有一個名爲 `date` 的屬性。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Calendar));
|
||||
return mockedComponent.children().childAt(1).props().date;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`CurrentDate` 的 `date` 屬性應該包含一段文本字符串。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Calendar));
|
||||
const prop = mockedComponent.children().childAt(1).props().date;
|
||||
return typeof prop === 'string' && prop.length > 0;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`date` 屬性應該通過調用 `Date()` 生成。
|
||||
|
||||
```js
|
||||
assert(/<CurrentDatedate={Date\(\)}\/>/.test(__helpers.removeWhiteSpace(code)));
|
||||
```
|
||||
|
||||
`CurrentDate` 組件應該把 `date` 屬性渲染在 `p` 標籤內。
|
||||
|
||||
```js
|
||||
let date = 'dummy date';
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(
|
||||
React.createElement(CurrentDate, { date })
|
||||
);
|
||||
return mockedComponent.find('p').html().includes(date);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<Calendar />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const CurrentDate = (props) => {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<p>The current date is: </p>
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class Calendar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>What date is it?</h3>
|
||||
{ /* Change code below this line */ }
|
||||
<CurrentDate />
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const CurrentDate = (props) => {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<p>The current date is: {props.date}</p>
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class Calendar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>What date is it?</h3>
|
||||
{ /* Change code below this line */ }
|
||||
<CurrentDate date={Date()} />
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,145 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403617a
|
||||
title: 將 State 作爲 Props 傳遞給子組件
|
||||
challengeType: 6
|
||||
forumTopicId: 301403
|
||||
dashedName: pass-state-as-props-to-child-components
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
在之前的挑戰中,看到了很多將 props 傳遞給子 JSX 元素和子 React 組件的例子。 你可能想知道那些 props 是從哪裏來的。 一個常見的模式是:有狀態組件中包含對應用程序很重要的 `state`,然後用它渲染子組件。 如果想讓這些組件能夠訪問該 `state` 的某些部分,就把這些部分作爲 props 傳入。
|
||||
|
||||
例如,有一個 `App` 組件可以渲染 `Navbar` 以及其他組件。 `App` 裏的 `state` 包含大量用戶信息,但 `Navbar` 只需要訪問用戶的用戶名,以便顯示它。 將該 `state` 作爲 prop 傳遞給`Navbar`組件。
|
||||
|
||||
這個模式說明了 React 中的一些重要範例。 第一個是*單向數據流*, state 沿着應用程序組件樹的一個方向流動,從有狀態父組件到子組件, 子組件只接收它們需要的 state 數據。 第二,複雜的有狀態應用程序可以分解成幾個,或者可能是一個單一的有狀態組件。 其餘組件只是從父組件簡單的接收 state 作爲 props,並從該 state 渲染 UI。 它開始創建一種分離,在這種分離中,state 管理在代碼的一部分中處理,而 UI 渲染在另一部分中處理。 將 state 邏輯與 UI 邏輯分離是 React 的關鍵原則之一。 當它被正確使用時,它使得複雜的、有狀態的應用程序的設計變得更容易管理。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`MyApp` 組件是有狀態的,並將 `Navbar` 組件渲染爲子組件。 將 `state` 的 `name` 屬性向下傳遞給子組件,然後在 `h1` 中顯示該 `name` ,h1 是 `Navbar` render方法的一部分。 `name` 應該顯示在文本 `Hello, my name is:` 後面。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyApp` 組件應該在內部渲染一個 `Navbar` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
return (
|
||||
mockedComponent.find('MyApp').length === 1 &&
|
||||
mockedComponent.find('Navbar').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Navbar` 組件應該接收 `MyApp` 的 state 中的 `name` 屬性作爲 props。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
const setState = () => {
|
||||
mockedComponent.setState({ name: 'TestName' });
|
||||
return waitForIt(() => mockedComponent.find('Navbar').props());
|
||||
};
|
||||
const navProps = await setState();
|
||||
assert(navProps.name === 'TestName');
|
||||
};
|
||||
```
|
||||
|
||||
`Navbar` 中的 `h1`元素應該渲染 prop `name`。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
const navH1Before = mockedComponent.find('Navbar').find('h1').text();
|
||||
const setState = () => {
|
||||
mockedComponent.setState({ name: 'TestName' });
|
||||
return waitForIt(() => mockedComponent.find('Navbar').find('h1').text());
|
||||
};
|
||||
const navH1After = await setState();
|
||||
assert(new RegExp('TestName').test(navH1After) && navH1After !== navH1Before);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyApp />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: 'CamperBot'
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{/* Change code below this line */}
|
||||
<Navbar />
|
||||
{/* Change code above this line */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class Navbar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{/* Change code below this line */}
|
||||
<h1>Hello, my name is: </h1>
|
||||
{/* Change code above this line */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: 'CamperBot'
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Navbar name={this.state.name}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
class Navbar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Hello, my name is: {this.props.name}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,159 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036167
|
||||
title: 將 class 組件渲染到 DOM 樹
|
||||
challengeType: 6
|
||||
forumTopicId: 301404
|
||||
dashedName: render-a-class-component-to-the-dom
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
還記不記得在之前的挑戰中使用 ReactDOM API 將 JSX 元素渲染到 DOM, 這與渲染 React 組件的過程十分相似。 過去的幾個挑戰主要針對組件和組合,因此渲染是在幕後完成的。 但是,如果不調用 ReactDOM API,編寫的任何 React 代碼都不會渲染到 DOM。
|
||||
|
||||
複習一下語法: `ReactDOM.render(componentToRender, targetNode)`。 第一個參數是要渲染的 React 組件。 第二個參數是要在其中渲染該組件的 DOM 節點。
|
||||
|
||||
傳遞到`ReactDOM.render()` 的React 組件與 JSX 元素略有不同。 對於 JSX 元素,傳入的是要渲染的元素的名稱。 但是,對於 React 組件,需要使用與渲染嵌套組件相同的語法,例如`ReactDOM.render(<ComponentToRender />, targetNode)`。 此語法用於 ES6 class 組件和函數組件都可以。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在後臺引入了 `Fruits` 和 `Vegetables` 組件。 將兩個組件渲染爲 `TypesOfFood` 組件的子組件,然後將 `TypesOfFood` 渲染到 DOM 節點, 在這個挑戰中,請渲染到 `id='challenge-node'`的 `div` 中。
|
||||
|
||||
# --hints--
|
||||
|
||||
`TypesOfFood` 組件應該返回單個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(TypesOfFood));
|
||||
return mockedComponent.children().type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`TypesOfFood` 組件應該在 `h1` 元素之後渲染 `Fruits` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(TypesOfFood));
|
||||
return mockedComponent.children().childAt(1).name() === 'Fruits';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`TypesOfFood` 組件應該在 `Fruits` 組件之後渲染 `Vegetables` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(TypesOfFood));
|
||||
return mockedComponent.children().childAt(2).name() === 'Vegetables';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`TypesOfFood` 組件應該渲染到 id 爲 `challenge-node` 的 `div`中。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const html = document.getElementById('challenge-node').childNodes[0]
|
||||
.innerHTML;
|
||||
return (
|
||||
html.includes(
|
||||
'<div><h2>Fruits:</h2><h4>Non-Citrus:</h4><ul><li>Apples</li><li>Blueberries</li><li>Strawberries</li><li>Bananas</li></ul><h4>Citrus:</h4><ul><li>Lemon</li><li>Lime</li><li>Orange</li><li>Grapefruit</li></ul></div>'
|
||||
) &&
|
||||
html.includes(
|
||||
'<div><h2>Vegetables:</h2><ul><li>Brussel Sprouts</li><li>Broccoli</li><li>Squash</li></ul></div>'
|
||||
)
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --before-user-code--
|
||||
|
||||
```jsx
|
||||
const Fruits = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Fruits:</h2>
|
||||
<h4>Non-Citrus:</h4>
|
||||
<ul>
|
||||
<li>Apples</li>
|
||||
<li>Blueberries</li>
|
||||
<li>Strawberries</li>
|
||||
<li>Bananas</li>
|
||||
</ul>
|
||||
<h4>Citrus:</h4>
|
||||
<ul>
|
||||
<li>Lemon</li>
|
||||
<li>Lime</li>
|
||||
<li>Orange</li>
|
||||
<li>Grapefruit</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const Vegetables = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Vegetables:</h2>
|
||||
<ul>
|
||||
<li>Brussel Sprouts</li>
|
||||
<li>Broccoli</li>
|
||||
<li>Squash</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class TypesOfFood extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Types of Food:</h1>
|
||||
{/* Change code below this line */}
|
||||
|
||||
{/* Change code above this line */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class TypesOfFood extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Types of Food:</h1>
|
||||
{/* Change code below this line */}
|
||||
<Fruits />
|
||||
<Vegetables />
|
||||
{/* Change code above this line */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
ReactDOM.render(<TypesOfFood />, document.getElementById('challenge-node'));
|
||||
```
|
@ -0,0 +1,303 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036188
|
||||
title: 根據 Props 有條件地渲染
|
||||
challengeType: 6
|
||||
forumTopicId: 301405
|
||||
dashedName: render-conditionally-from-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
到目前爲止,你已經看到如何使用 `if/else`、`&&` 以及三元運算符(`condition ? expressionIfTrue : expressionIfFalse`)在不同條件下運行不同的代碼。 然而,還有一個重要的話題需要討論,將這些概念中的任何一個或所有概念與另一個強大的 React 功能 props 結合起來。 使用 props 有條件地渲染代碼在 React 開發人員中很常見——也就是說:他們使用給定 prop 的值來自動決定渲染什麼。
|
||||
|
||||
在這個挑戰中,將設置一個子組件來根據 props 做出渲染決定。 可以使用三元運算符,但是可以看到過去幾個挑戰中涵蓋的其他幾個概念在這種情況下可能同樣有用。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器有兩個部分爲你定義的組件:一個名爲 `GameOfChance` 的父組件和一個名爲 `Results` 的子組件。 它們被用來創建一個簡單的遊戲,用戶按下按鈕來看它們是贏還是輸。
|
||||
|
||||
首先,需要一個簡單的表達式,每次運行時都會隨機返回一個不同的值。 可以使用 `Math.random()`。 每次調用此方法時,此方法返回 `0`(包括)和 `1`(不包括)之間的值。 因此,對於50/50的機率,請在表達式中使用 `Math.random() >= .5`。 從統計學上講,這個表達式有 50% 的機率返回 `true`,另外 50% 返回 `false`。 在第 render 方法裏,用此表達式替換 `null` 以完成變量聲明。
|
||||
|
||||
現在了一個表達式,可以使用該表達式在代碼中做出隨機決策。 接下來,需要實現此功能。 將 `Results` 組件渲染爲 `GameOfChance` 的子 組件,並將 `expression` 作爲名爲 `fiftyFifty` 的 prop 傳入 。 在 `Results` 組件中,編寫一個三元表達式來渲染 `h1` 元素的文本。`GameOfChance` 傳來的 prop `fiftyFifty` 來決定渲染文本 `You Win!` 還是 `You Lose!`。 最後,確保 `handleClick()` 方法正確計算每個回合,以便用戶知道他們玩過多少次。 這也可以讓用戶知道組件實際上已經更新,以防他們連續贏兩次或輸兩次時自己不知道。
|
||||
|
||||
# --hints--
|
||||
|
||||
`GameOfChance` 組件應該存在並渲染到頁面。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(GameOfChance)).find('GameOfChance').length,
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
`GameOfChance` 應該返回單個 `button` 元素。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(GameOfChance)).find('button').length,
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
`GameOfChance` 應該返回 `Results` 組件的一個實例,它有一個名爲 `fiftyFifty` 的 prop。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(GameOfChance)).find('Results').length ===
|
||||
1 &&
|
||||
Enzyme.mount(React.createElement(GameOfChance))
|
||||
.find('Results')
|
||||
.props()
|
||||
.hasOwnProperty('fiftyFifty') === true
|
||||
);
|
||||
```
|
||||
|
||||
`GameOfChance` 的 state 應該使用值爲 `1` 的 `counter` 屬性來初始化。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(GameOfChance)).state().counter,
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
當 `GameOfChance` 組件第一次渲染到 DOM 時,應該返回一個 `p` 元素,其內部文本爲 `Turn: 1`。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(GameOfChance)).find('p').text(),
|
||||
'Turn: 1'
|
||||
);
|
||||
```
|
||||
|
||||
每次點擊按鈕,counter 應該增加 1,並且一個包含文本 `Turn: N` 的 `p` 元素應該渲染到 DOM,其中 `N` 是 counter 的值。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const comp = Enzyme.mount(React.createElement(GameOfChance));
|
||||
const simulate = () => {
|
||||
comp.find('button').simulate('click');
|
||||
};
|
||||
const result = () => ({
|
||||
count: comp.state('counter'),
|
||||
text: comp.find('p').text()
|
||||
});
|
||||
const _1 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _2 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _3 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _4 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _5 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _1_val = _1();
|
||||
const _2_val = _2();
|
||||
const _3_val = _3();
|
||||
const _4_val = _4();
|
||||
const _5_val = _5();
|
||||
assert(
|
||||
_1_val.count === 2 &&
|
||||
_1_val.text === 'Turn: 2' &&
|
||||
_2_val.count === 3 &&
|
||||
_2_val.text === 'Turn: 3' &&
|
||||
_3_val.count === 4 &&
|
||||
_3_val.text === 'Turn: 4' &&
|
||||
_4_val.count === 5 &&
|
||||
_4_val.text === 'Turn: 5' &&
|
||||
_5_val.count === 6 &&
|
||||
_5_val.text === 'Turn: 6'
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
當 `GameOfChance` 組件第一次掛載到 DOM 上時,每次按鈕被點擊,都應該返回一個 `h1` 元素,元素中隨機渲染 `You Win!` 或者 `You Lose!`。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const comp = Enzyme.mount(React.createElement(GameOfChance));
|
||||
const simulate = () => {
|
||||
comp.find('button').simulate('click');
|
||||
};
|
||||
const result = () => ({
|
||||
h1: comp.find('h1').length,
|
||||
text: comp.find('h1').text()
|
||||
});
|
||||
const _1 = result();
|
||||
const _2 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _3 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _4 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _5 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _6 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _7 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _8 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _9 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _10 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _2_val = _2();
|
||||
const _3_val = _3();
|
||||
const _4_val = _4();
|
||||
const _5_val = _5();
|
||||
const _6_val = _6();
|
||||
const _7_val = _7();
|
||||
const _8_val = _8();
|
||||
const _9_val = _9();
|
||||
const _10_val = _10();
|
||||
const __text = new Set([
|
||||
_1.text,
|
||||
_2_val.text,
|
||||
_3_val.text,
|
||||
_4_val.text,
|
||||
_5_val.text,
|
||||
_6_val.text,
|
||||
_7_val.text,
|
||||
_8_val.text,
|
||||
_9_val.text,
|
||||
_10_val.text
|
||||
]);
|
||||
const __h1 = new Set([
|
||||
_1.h1,
|
||||
_2_val.h1,
|
||||
_3_val.h1,
|
||||
_4_val.h1,
|
||||
_5_val.h1,
|
||||
_6_val.h1,
|
||||
_7_val.h1,
|
||||
_8_val.h1,
|
||||
_9_val.h1,
|
||||
_10_val.h1
|
||||
]);
|
||||
assert(__text.size === 2 && __h1.size === 1);
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<GameOfChance />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class Results extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
{/* Change code below this line */}
|
||||
return <h1></h1>;
|
||||
{/* Change code above this line */}
|
||||
}
|
||||
}
|
||||
|
||||
class GameOfChance extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
counter: 1
|
||||
};
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick() {
|
||||
this.setState(prevState => {
|
||||
// Complete the return statement:
|
||||
return {
|
||||
counter: prevState
|
||||
}
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const expression = null; // Change this line
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.handleClick}>Play Again</button>
|
||||
{/* Change code below this line */}
|
||||
|
||||
{/* Change code above this line */}
|
||||
<p>{'Turn: ' + this.state.counter}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class Results extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <h1>{this.props.fiftyFifty ? 'You Win!' : 'You Lose!'}</h1>;
|
||||
}
|
||||
}
|
||||
|
||||
class GameOfChance extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
counter: 1
|
||||
};
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick() {
|
||||
this.setState(prevState => {
|
||||
return {
|
||||
counter: prevState.counter + 1
|
||||
}
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const expression = Math.random() >= 0.5;
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.handleClick}>Play Again</button>
|
||||
<Results fiftyFifty={expression} />
|
||||
<p>{'Turn: ' + this.state.counter}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,75 @@
|
||||
---
|
||||
id: 5a24bbe0dba28a8d3cbd4c5f
|
||||
title: 渲染 HTML 元素爲 DOM 樹
|
||||
challengeType: 6
|
||||
forumTopicId: 301406
|
||||
dashedName: render-html-elements-to-the-dom
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
到目前爲止,已經瞭解到 JSX 是一種在 JavaScript 中編寫可讀 HTML 的便捷工具。 在 React 中,可以使用它的的渲染 API(ReactDOM)將此 JSX 直接渲染到 HTML DOM。
|
||||
|
||||
ReactDOM 提供了一個簡單的方法來將 React 元素呈現給 DOM,如下所示:`ReactDOM.render(componentToRender, targetNode)`,其中第一個參數是要渲染的 React 元素或組件,第二個參數是組件將要渲染到的 DOM 節點。
|
||||
|
||||
如你所料,必須在 JSX 元素聲明之後調用 `ReactDOM.render()`,就像在使用變量之前必須聲明它一樣。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器有一個簡單的 JSX 組件。 使用 `ReactDOM.render()` 方法將該組件渲染到頁面。 可以將定義好的 JSX 元素直接作爲第一個參數傳入,然後使用 `document.getElementById()` 來選擇要渲染到的 DOM 節點, 在這個挑戰中,請渲染到 `id='challenge-node'` 的 `div` 中。 確保沒有修改 `JSX` 常量。
|
||||
|
||||
# --hints--
|
||||
|
||||
常量 `JSX` 應該返回一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(JSX.type === 'div');
|
||||
```
|
||||
|
||||
`div` 應該包含一個 `h1` 標籤作爲第一個元素。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[0].type === 'h1');
|
||||
```
|
||||
|
||||
`div` 應該包含一個 `p` 標籤作爲第二個元素。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[1].type === 'p');
|
||||
```
|
||||
|
||||
提供的 JSX 元素應該渲染到 id 爲 `challenge-node` 的 DOM 節點。
|
||||
|
||||
```js
|
||||
assert(
|
||||
document.getElementById('challenge-node').childNodes[0].innerHTML ===
|
||||
'<h1>Hello World</h1><p>Lets render this to the DOM</p>'
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const JSX = (
|
||||
<div>
|
||||
<h1>Hello World</h1>
|
||||
<p>Lets render this to the DOM</p>
|
||||
</div>
|
||||
);
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const JSX = (
|
||||
<div>
|
||||
<h1>Hello World</h1>
|
||||
<p>Lets render this to the DOM</p>
|
||||
</div>
|
||||
);
|
||||
// Change code below this line
|
||||
ReactDOM.render(JSX, document.getElementById('challenge-node'));
|
||||
```
|
@ -0,0 +1,76 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403618d
|
||||
title: 用 renderToString 在服務器上渲染 React
|
||||
challengeType: 6
|
||||
forumTopicId: 301407
|
||||
dashedName: render-react-on-the-server-with-rendertostring
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
到目前爲止,已經能夠在客戶端上渲染 React 組件, 一般來說我們都是這麼做的。 然而,在一些用例中,需要在服務器上渲染一個 React 組件。 由於 React 是一個 JavaScript 視圖庫,所以通常使用 Node 讓 JavaScript 運行在服務器上。 事實上,React 提供了一個可用於此目的的 `renderToString()` 方法。
|
||||
|
||||
有兩個關鍵原因可以解釋爲什麼服務器上的渲染可能會在真實世界的應用程序中使用。 首先,如果不這樣做,當 React 應用程序最初加載到瀏覽器時,它將包含一個代碼量很少的 HTML 文件和一大堆 JavaScript。 這對於搜索引擎來說可能不太理想,因爲它們試圖爲網頁內容生成索引,以便人們可以找到這個應用。 如果在服務器上渲染初始 HTML 標記並將其發送到客戶端,則初始頁面加載的內容包含搜索引擎可以抓取的所有頁面標記。 其次,這創造了更快的初始頁面加載體驗,因爲渲染的 HTML 代碼量要比整個應用程序的 JavaScript 代碼小。 React 仍然能夠識別你的應用並在初始加載後進行管理。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`renderToString()` 方法由 `ReactDOMServer` 提供,在這裏已爲你定義成全局變量。 這個方法接收一個 React 元素作爲參數。 用它來把 `App` 渲染成字符串。
|
||||
|
||||
# --hints--
|
||||
|
||||
`App` 組件應該使用 `ReactDOMServer.renderToString` 渲染一個字符串。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
getUserInput('index')
|
||||
.replace(/ /g, '')
|
||||
.includes('ReactDOMServer.renderToString(<App/>)') &&
|
||||
Enzyme.mount(React.createElement(App)).children().name() === 'div'
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --before-user-code--
|
||||
|
||||
```jsx
|
||||
var ReactDOMServer = { renderToString(x) { return null; } };
|
||||
```
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <div/>
|
||||
}
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <div/>
|
||||
}
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
ReactDOMServer.renderToString(<App/>);
|
||||
```
|
@ -0,0 +1,119 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036172
|
||||
title: 以另一種方式在用戶界面中渲染狀態
|
||||
challengeType: 6
|
||||
forumTopicId: 301408
|
||||
dashedName: render-state-in-the-user-interface-another-way
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
還有另一種方法可以訪問組件中的 `state`。 在 `render()` 方法中,在 `return` 語句之前,可以直接編寫 JavaScript。 例如,可以聲明函數、從 `state` 或 `props` 中訪問數據、對此數據執行計算等。 然後,可以將任何數據賦值給 `return` 語句中可以訪問的變量。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在 `MyComponent` 的 render 方法中,定義一個名爲 `name` 的 `const`(常量),並將其設置爲組件 `state` 中的 name 值。 因爲可以直接在代碼部分編寫 JavaScript,所以不需要用大括號括起來。
|
||||
|
||||
接下來,在 return 語句中,在 `h1` 標籤中渲染變量 `name` 的值。 記住,在 return 語句中需要使用 JSX 語法(用到 JavaScript 的花括號)。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該有一個鍵 `name`,其值 `freeCodeCamp` 存儲在其 state 中。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('name') ===
|
||||
'freeCodeCamp'
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 應該在 `div` 中渲染一個 `h1` 標題。
|
||||
|
||||
```js
|
||||
assert(
|
||||
/<div><h1>.*<\/h1><\/div>/.test(
|
||||
Enzyme.mount(React.createElement(MyComponent)).html()
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
渲染的 `h1` 標籤應該包含 `{name}` 的引用。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(/<h1>\n*\s*\{\s*name\s*\}\s*\n*<\/h1>/.test(getUserInput('index')));
|
||||
```
|
||||
|
||||
渲染的 `h1` 標題中應該包含一段文本,這段文本是從組件的 state 中渲染出來的。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const first = () => {
|
||||
mockedComponent.setState({ name: 'TestName' });
|
||||
return waitForIt(() => mockedComponent.html());
|
||||
};
|
||||
const firstValue = await first();
|
||||
assert(firstValue === '<div><h1>TestName</h1></div>');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: 'freeCodeCamp'
|
||||
}
|
||||
}
|
||||
render() {
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: 'freeCodeCamp'
|
||||
}
|
||||
}
|
||||
render() {
|
||||
// Change code below this line
|
||||
const name = this.state.name;
|
||||
// Change code above this line
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<h1>{name}</h1>
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,113 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036171
|
||||
title: 在用戶界面中渲染狀態
|
||||
challengeType: 6
|
||||
forumTopicId: 301409
|
||||
dashedName: render-state-in-the-user-interface
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
定義了組件的初始 state 之後,就可以在要渲染的 UI 中顯示它。 如果組件是有狀態的,它將始終可以訪問 `render()` 方法中 `state` 的數據。 就可以使用 `this.state` 訪問數據。
|
||||
|
||||
如果想在 render 方法的 `return` 中訪問 state 值,必須把這個值用花括號括起來。
|
||||
|
||||
`state` 是 React 組件中最強大的特性之一, 它可以跟蹤應用程序中的重要數據,並根據數據的變化渲染 UI。 如果數據發生變化,UI 也會隨之改變。 React 使用所謂的虛擬 DOM 來跟蹤幕後的變化。 當 state 數據更新時,它會使用該數據觸發組件的重新渲染 -- 包括接收 prop 數據的子組件。 React 只在必要的時候更新實際的 DOM, 這意味着你不必擔心 DOM 的變更, 只需聲明 UI 的外觀即可。
|
||||
|
||||
注意,如果組件是有狀態的,其它組件並不知道它的 `state`。 它的 `state` 是完全封裝的,或者是侷限於組件本身的,除非你將 state 數據作爲 `props` 傳遞給子組件。 封裝 `state` 的概念非常重要,因爲它允許編寫特定的邏輯,然後將該邏輯包含並隔離在代碼中的某個位置。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在代碼編輯器中,`MyComponent` 是一個有狀態組件, 在組件的 render 方法中定義一個`h1`標籤,該方法從組件的 state 渲染 `name` 的值。
|
||||
|
||||
**注意:** `h1` 應該只渲染來自 `state` 的值。 在 JSX 中,使用花括號 `{ }` 編寫的任何代碼都將被視爲 JavaScript。 因此,要訪問 `state` 中的值,只需將引用括在花括號中即可。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該有一個鍵 `name`,其值 `freeCodeCamp` 存儲在其 state 中。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('name') ===
|
||||
'freeCodeCamp'
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 應該在 `div` 中渲染一個 `h1` 標題。
|
||||
|
||||
```js
|
||||
assert(
|
||||
/<div><h1>.*<\/h1><\/div>/.test(
|
||||
Enzyme.mount(React.createElement(MyComponent)).html()
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
渲染的 `h1` 標題中應該只包含一段文本,這段文本是從組件的 state 中渲染出來的。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const first = () => {
|
||||
mockedComponent.setState({ name: 'TestName' });
|
||||
return waitForIt(() => mockedComponent.html());
|
||||
};
|
||||
const firstValue = await first();
|
||||
const getValue = firstValue.replace(/\s/g, '');
|
||||
assert(getValue === '<div><h1>TestName</h1></div>');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: 'freeCodeCamp'
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: 'freeCodeCamp'
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<h1>{this.state.name}</h1>
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,155 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036184
|
||||
title: 使用 If-Else 條件進行渲染
|
||||
challengeType: 6
|
||||
forumTopicId: 301410
|
||||
dashedName: render-with-an-if-else-condition
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
使用 JavaScript 控制渲染視圖的另一個應用是按條件渲染元素。 當條件爲真時,將呈現一個視圖, 反之,則呈現另一種視圖。 可以在 React 組件的 `render()` 方法中使用的標準 `if/else` 語句來實現這一點。
|
||||
|
||||
# --instructions--
|
||||
|
||||
MyComponent 的 state 中包含一個 `boolean`(布爾值),用於跟蹤是否要在 UI 中顯示某個元素。 `button` 切換此值的狀態。 目前,它每次都呈現相同的 UI。 用 `if/else` 語句重寫 `render()` 方法,如果 `display` 爲 `true` 則返回當前標記。 否則,返回不帶 `h1` 元素的標記。
|
||||
|
||||
**注意:** 寫 `if/else` 語句才能通過測試, 使用三元運算符是不會通過的。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該存在並被渲染。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return mockedComponent.find('MyComponent').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
當 `display` 被設置爲 `true` 時,`div`、`button` 和 `h1` 標籤應該被渲染。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const state_1 = () => {
|
||||
mockedComponent.setState({ display: true });
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated = await state_1();
|
||||
assert(
|
||||
mockedComponent.find('div').length === 1 &&
|
||||
mockedComponent.find('div').children().length === 2 &&
|
||||
mockedComponent.find('button').length === 1 &&
|
||||
mockedComponent.find('h1').length === 1
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
當 `display` 被設置爲 `false` 時,只有 `div` 和 `button` 應該被渲染。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const state_1 = () => {
|
||||
mockedComponent.setState({ display: false });
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated = await state_1();
|
||||
assert(
|
||||
mockedComponent.find('div').length === 1 &&
|
||||
mockedComponent.find('div').children().length === 1 &&
|
||||
mockedComponent.find('button').length === 1 &&
|
||||
mockedComponent.find('h1').length === 0
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Render 方法中應該使用 `if/else` 語句來檢查 `this.state.display` 的條件。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
getUserInput('index').includes('if') &&
|
||||
getUserInput('index').includes('else')
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
display: true
|
||||
}
|
||||
this.toggleDisplay = this.toggleDisplay.bind(this);
|
||||
}
|
||||
toggleDisplay() {
|
||||
this.setState((state) => ({
|
||||
display: !state.display
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
// Change code below this line
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.toggleDisplay}>Toggle Display</button>
|
||||
<h1>Displayed!</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
display: true
|
||||
}
|
||||
this.toggleDisplay = this.toggleDisplay.bind(this);
|
||||
}
|
||||
toggleDisplay() {
|
||||
this.setState((state) => ({
|
||||
display: !state.display
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
// Change code below this line
|
||||
if (this.state.display) {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.toggleDisplay}>Toggle Display</button>
|
||||
<h1>Displayed!</h1>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.toggleDisplay}>Toggle Display</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,145 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403616f
|
||||
title: 複習使用無狀態函數組件的 Props
|
||||
challengeType: 6
|
||||
forumTopicId: 301411
|
||||
dashedName: review-using-props-with-stateless-functional-components
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
除了上一個挑戰,一直在將 props 傳遞給無狀態的函數組件。 這些組件就像純函數, 它們接收 props 作爲輸入,並在每次傳遞相同 props 時返回相同的視圖。 你可能好奇什麼是狀態,下一個挑戰將會更詳細地講述它。 在此之前,我們先來回顧一下組件的術語。
|
||||
|
||||
*無狀態函數組件*是一個函數,它接收 props 作爲輸入並返回 JSX。 另一方面,*無狀態組件*是一個類,它擴展了`React.Component`,但是不使用內部狀態(下一個挑戰中討論)。 最後,*狀態組件*是指維護其自身內部狀態的組件, 它簡稱組件或 React 組件。
|
||||
|
||||
一種常見的應用模式是儘可能減少狀態組件並創建無狀態的函數組件。 這有助於將狀態管理包含到應用程序的特定區域。 反過來,通過更容易地跟蹤狀態變化如何影響其行爲,可以改善應用程序的開發和維護。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在代碼編輯器中有一個 `CampSite` 組件,它把 `Camper` 組件渲染爲自己的子組件。 定義 `Camper` 組件,併爲其分配默認 props `{ name: 'CamperBot' }`。 可以在 `Camper` 組件內部渲染任何你想要的代碼,但是要確保有一個 `p` 元素,它只包含作爲 `prop` 傳遞的 `name` 值。 最後,在 `Camper` 組件上定義 `propTypes`,要求提供 `name` 作爲 prop,並驗證它是 `string` 類型。
|
||||
|
||||
# --hints--
|
||||
|
||||
應該渲染 `CampSite` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(CampSite));
|
||||
return mockedComponent.find('CampSite').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應該渲染 `Camper` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(CampSite));
|
||||
return mockedComponent.find('Camper').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Camper` 組件應該包含默認 props,它將字符串 `CamperBot` 賦值給關鍵字 `name`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
/Camper.defaultProps={name:(['"`])CamperBot\1,?}/.test(
|
||||
__helpers.removeWhiteSpace(code)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`Camper` 組件應該包含 `string` 類型的 `name` prop。
|
||||
|
||||
```js
|
||||
assert(
|
||||
/Camper.propTypes={name:PropTypes.string.isRequired,?}/.test(
|
||||
__helpers.removeWhiteSpace(code)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`Camper` 組件應該包含 `p` 元素,元素的文本是 prop 的 `name`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(CampSite));
|
||||
return (
|
||||
mockedComponent.find('p').text() ===
|
||||
mockedComponent.find('Camper').props().name
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --before-user-code--
|
||||
|
||||
```jsx
|
||||
var PropTypes = {
|
||||
string: { isRequired: true }
|
||||
};
|
||||
```
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<CampSite />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class CampSite extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Camper/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class CampSite extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Camper/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
// Change code below this line
|
||||
|
||||
const Camper = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<p>{props.name}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Camper.propTypes = {
|
||||
name: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
Camper.defaultProps = {
|
||||
name: 'CamperBot'
|
||||
};
|
||||
```
|
@ -0,0 +1,143 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036173
|
||||
title: 用 this.setState 設置狀態
|
||||
challengeType: 6
|
||||
forumTopicId: 301412
|
||||
dashedName: set-state-with-this-setstate
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
前面的挑戰涵蓋了組件的 `state` 以及如何在 `constructor` 中初始化 state。 還有一種方法可以更改組件的 `state`, React 提供了 `setState` 方法來更新組件的 `state`。 在組件類中調用 `setState` 方法如下所示:`this.setState()`,傳入鍵值對的對象, 其中鍵是 state 屬性,值是更新後的 state 數據。 例如,如果我們在 state 中存儲 `username`,並想要更新它,代碼如下所示:
|
||||
|
||||
```jsx
|
||||
this.setState({
|
||||
username: 'Lewis'
|
||||
});
|
||||
```
|
||||
|
||||
React 要求永遠不要直接修改 `state`,而是在 state 發生改變時始終使用 `this.setState()`。 此外,應該注意,React 可以批量處理多個 state 更新以提高性能。 這意味着通過 `setState` 方法進行的 state 更新可以是異步的。 `setState` 方法有一種替代語法可以解決異步問題, 雖然這很少用到,但是最好還是記住它! 有關詳細信息,請參閱[React 文檔](https://facebook.github.io/react/docs/state-and-lifecycle.html)。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器中有一個 `button` 元素,它有一個 `onClick()` 處理程序。 當 `button` 在瀏覽器中接收到單擊事件時觸發此處理程序,並運行 `MyComponent` 中定義的 `handleClick` 方法。 在 `handleClick` 方法中,使用 `this.setState()` 更新組件的 `state`。 設置 `state` 中的 `name` 屬性爲字符串 `React Rocks!`。
|
||||
|
||||
單擊按鈕查看渲染的 state 的更新。 如果不完全理解單擊處理程序代碼在此時的工作方式,請不要擔心。 在接下來的挑戰中會有講述。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 的 state 應該使用鍵值對 `{ name: Initial State }` 來初始化。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('name') ===
|
||||
'Initial State'
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 應該渲染一個 `h1` 標題。
|
||||
|
||||
```js
|
||||
assert(Enzyme.mount(React.createElement(MyComponent)).find('h1').length === 1);
|
||||
```
|
||||
|
||||
渲染的 `h1` 標題中應該包含一段文本,這段文本是從組件的 state 中渲染出來的。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const first = () => {
|
||||
mockedComponent.setState({ name: 'TestName' });
|
||||
return waitForIt(() => mockedComponent.html());
|
||||
};
|
||||
const firstValue = await first();
|
||||
assert(/<h1>TestName<\/h1>/.test(firstValue));
|
||||
};
|
||||
```
|
||||
|
||||
調用 `MyComponent` 的 `handleClick` 方法應該將 state 的 name 屬性設置爲 `React Rocks!`。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const first = () => {
|
||||
mockedComponent.setState({ name: 'Before' });
|
||||
return waitForIt(() => mockedComponent.state('name'));
|
||||
};
|
||||
const second = () => {
|
||||
mockedComponent.instance().handleClick();
|
||||
return waitForIt(() => mockedComponent.state('name'));
|
||||
};
|
||||
const firstValue = await first();
|
||||
const secondValue = await second();
|
||||
assert(firstValue === 'Before' && secondValue === 'React Rocks!');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: 'Initial State'
|
||||
};
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick() {
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.handleClick}>Click Me</button>
|
||||
<h1>{this.state.name}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: 'Initial State'
|
||||
};
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick() {
|
||||
// Change code below this line
|
||||
this.setState({
|
||||
name: 'React Rocks!'
|
||||
});
|
||||
// Change code above this line
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick = {this.handleClick}>Click Me</button>
|
||||
<h1>{this.state.name}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,146 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036185
|
||||
title: Use && for a More Concise Conditional
|
||||
challengeType: 6
|
||||
forumTopicId: 301413
|
||||
dashedName: use--for-a-more-concise-conditional
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`if/else` 語句在上一次挑戰中是有效的,但是有一種更簡潔的方法可以達到同樣的結果。 假設正在跟蹤組件中的幾個條件,並且希望根據這些條件中的每一個來渲染不同的元素。 如果你寫了很多 `else if` 語句來返回稍微不同的 UI,你可能會寫很多重複代碼,這就留下了出錯的空間。 相反,你可以使用 `&&` 邏輯運算符以更簡潔的方式執行條件邏輯。 這是完全可行的,因爲你希望檢查條件是否爲 `true`。如果是,則返回一些標記。 這裏有一個例子:
|
||||
|
||||
```jsx
|
||||
{condition && <p>markup</p>}
|
||||
```
|
||||
|
||||
如果 `condition` 爲 `true`,則返回標記。 如果條件爲 `false` ,則在評估 `condition` 後操作將立即返回 `false`,並且不返回任何內容。 可以將這些語句直接包含在 JSX 中,並通過在每個條件後面寫 `&&` 來將多個條件串在一起。 這允許你在 `render()` 方法中處理更復雜的條件邏輯,而無需重複大量代碼。
|
||||
|
||||
# --instructions--
|
||||
|
||||
再來看看前面的示例,`h1` 還是在 `display` 爲 `true` 時渲染,但使用 `&&` 邏輯運算符代替 `if/else` 語句。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該存在並渲染。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return mockedComponent.find('MyComponent').length;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
當 `display` 被設置爲 `true` 時,`div`、`button` 和 `h1` 標籤應該渲染。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const state_1 = () => {
|
||||
mockedComponent.setState({ display: true });
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated = await state_1();
|
||||
assert(
|
||||
updated.find('div').length === 1 &&
|
||||
updated.find('div').children().length === 2 &&
|
||||
updated.find('button').length === 1 &&
|
||||
updated.find('h1').length === 1
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
當 `display` 被設置爲 `false` 時,只有 `div` 和 `button` 應該渲染。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const state_1 = () => {
|
||||
mockedComponent.setState({ display: false });
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated = await state_1();
|
||||
assert(
|
||||
updated.find('div').length === 1 &&
|
||||
updated.find('div').children().length === 1 &&
|
||||
updated.find('button').length === 1 &&
|
||||
updated.find('h1').length === 0
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
render 方法應該使用 `&&` 邏輯運算符來檢查 `this.state.display` 的條件。
|
||||
|
||||
```js
|
||||
(getUserInput) => assert(getUserInput('index').includes('&&'));
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
display: true
|
||||
}
|
||||
this.toggleDisplay = this.toggleDisplay.bind(this);
|
||||
}
|
||||
toggleDisplay() {
|
||||
this.setState(state => ({
|
||||
display: !state.display
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
// Change code below this line
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.toggleDisplay}>Toggle Display</button>
|
||||
<h1>Displayed!</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
display: true
|
||||
}
|
||||
this.toggleDisplay = this.toggleDisplay.bind(this);
|
||||
}
|
||||
toggleDisplay() {
|
||||
this.setState(state => ({
|
||||
display: !state.display
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
// Change code below this line
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.toggleDisplay}>Toggle Display</button>
|
||||
{this.state.display && <h1>Displayed!</h1>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,280 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036187
|
||||
title: 使用三元表達式進行條件渲染
|
||||
challengeType: 6
|
||||
forumTopicId: 301414
|
||||
dashedName: use-a-ternary-expression-for-conditional-rendering
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
在繼續使用動態渲染技術之前,還有最後一種方法可以渲染想要的東西,它使用內置的 JavaScript 條件:<dfn>三元運算符</dfn>。 三元運算符經常被用作 JavaScript 中 `if/else` 語句的縮寫。 它們不像傳統的 `if/else` 語句那樣強大,但是在 React 開發人員中非常流行, 原因之一就是 JSX 的編譯原理,`if/else` 語句不能直接插入到 JSX 代碼中。 可能你在前幾個挑戰就注意到了這一點——當需要 `if/else` 語句時,它總是在 `return` 語句的*外面*。 如果想在 JSX 中實現條件邏輯,三元表達式是一個很好的選擇。 回想一下,三元運算符有三個部分,但是可以將多個三元表達式組合在一起。 以下是基本語法:
|
||||
|
||||
```jsx
|
||||
condition ? expressionIfTrue : expressionIfFalse;
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器在 `CheckUserAge` 組件的 `render()` 方法中定義了三個常量, 它們分別是 `buttonOne`、`buttonTwo` 和 `buttonThree`。 每個都分配了一個表示按鈕元素的簡單 JSX 表達式。 首先,使用 `input` 和 `userAge` 初始化 `CheckUserAge` 的 state,並將其值設置爲空字符串。
|
||||
|
||||
一旦組件將信息渲染給頁面,用戶應該有一種方法與之交互。 在組件的 `return` 語句中,設置一個實現以下邏輯的三元表達式:當頁面首次加載時,將提交按鈕 `buttonOne` 渲染到頁面。 然後,當用戶輸入年齡並點擊該按鈕時,根據年齡渲染不同的按鈕。 如果用戶輸入的數字小於`18`,則渲染`buttonThree`。 如果用戶輸入的數字大於或等於`18`,則渲染`buttonTwo`。
|
||||
|
||||
# --hints--
|
||||
|
||||
`CheckUserAge` 組件應該渲染出單個 `input` 元素和單個 `button` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(CheckUserAge)).find('div').find('input')
|
||||
.length === 1 &&
|
||||
Enzyme.mount(React.createElement(CheckUserAge)).find('div').find('button')
|
||||
.length === 1
|
||||
);
|
||||
```
|
||||
|
||||
`CheckUserAge` 組件的 state 應該用 `userAge` 屬性和 `input` 屬性初始化,並且這兩個屬性的值都被設置爲空字符串。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(CheckUserAge)).state().input === '' &&
|
||||
Enzyme.mount(React.createElement(CheckUserAge)).state().userAge === ''
|
||||
);
|
||||
```
|
||||
|
||||
當 `CheckUserAge` 組件首次渲染到 DOM 時,`button` 內部的文本應爲 Submit。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(CheckUserAge)).find('button').text() ===
|
||||
'Submit'
|
||||
);
|
||||
```
|
||||
|
||||
當小於 18 的數字輸入到 `input` 元素中,並點擊該`button` 時,該 `button` 的內部文本應該是 `You Shall Not Pass`。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge));
|
||||
const initialButton = mockedComponent.find('button').text();
|
||||
const enter3AndClickButton = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: '3' } });
|
||||
mockedComponent.find('button').simulate('click');
|
||||
mockedComponent.update();
|
||||
return mockedComponent.find('button').text();
|
||||
};
|
||||
const enter17AndClickButton = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: '17' } });
|
||||
mockedComponent.find('button').simulate('click');
|
||||
mockedComponent.update();
|
||||
return mockedComponent.find('button').text();
|
||||
};
|
||||
const userAge3 = enter3AndClickButton();
|
||||
const userAge17 = enter17AndClickButton();
|
||||
assert(
|
||||
initialButton === 'Submit' &&
|
||||
userAge3 === 'You Shall Not Pass' &&
|
||||
userAge17 === 'You Shall Not Pass'
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
當大於或等於 18 的數字輸入到 `input` 元素中,並點擊該 `button` 時,該 `button` 的內部文本應該是 `You May Enter`。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge));
|
||||
const initialButton = mockedComponent.find('button').text();
|
||||
const enter18AndClickButton = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: '18' } });
|
||||
mockedComponent.find('button').simulate('click');
|
||||
mockedComponent.update();
|
||||
return mockedComponent.find('button').text();
|
||||
};
|
||||
const enter35AndClickButton = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: '35' } });
|
||||
mockedComponent.find('button').simulate('click');
|
||||
mockedComponent.update();
|
||||
return mockedComponent.find('button').text();
|
||||
};
|
||||
const userAge18 = enter18AndClickButton();
|
||||
const userAge35 = enter35AndClickButton();
|
||||
assert(
|
||||
initialButton === 'Submit' &&
|
||||
userAge18 === 'You May Enter' &&
|
||||
userAge35 === 'You May Enter'
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
提交了一個數字之後,並再次更改了 `input` 的值,該 `button` 內部文本應該變回 `Submit`。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge));
|
||||
const enter18AndClickButton = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: '18' } });
|
||||
mockedComponent.find('button').simulate('click');
|
||||
mockedComponent.update();
|
||||
return mockedComponent.find('button').text();
|
||||
};
|
||||
const changeInputDontClickButton = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: '5' } });
|
||||
mockedComponent.update();
|
||||
return mockedComponent.find('button').text();
|
||||
};
|
||||
const enter10AndClickButton = () => {
|
||||
mockedComponent
|
||||
.find('input')
|
||||
.simulate('change', { target: { value: '10' } });
|
||||
mockedComponent.find('button').simulate('click');
|
||||
mockedComponent.update();
|
||||
return mockedComponent.find('button').text();
|
||||
};
|
||||
const userAge18 = enter18AndClickButton();
|
||||
const changeInput1 = changeInputDontClickButton();
|
||||
const userAge10 = enter10AndClickButton();
|
||||
const changeInput2 = changeInputDontClickButton();
|
||||
assert(
|
||||
userAge18 === 'You May Enter' &&
|
||||
changeInput1 === 'Submit' &&
|
||||
userAge10 === 'You Shall Not Pass' &&
|
||||
changeInput2 === 'Submit'
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
你的代碼不應該包含任何 `if/else` 語句。
|
||||
|
||||
```js
|
||||
assert(
|
||||
new RegExp(/(\s|;)if(\s|\()/).test(
|
||||
Enzyme.mount(React.createElement(CheckUserAge)).instance().render.toString()
|
||||
) === false
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<CheckUserAge />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const inputStyle = {
|
||||
width: 235,
|
||||
margin: 5
|
||||
};
|
||||
|
||||
class CheckUserAge extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
this.submit = this.submit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleChange(e) {
|
||||
this.setState({
|
||||
input: e.target.value,
|
||||
userAge: ''
|
||||
});
|
||||
}
|
||||
submit() {
|
||||
this.setState(state => ({
|
||||
userAge: state.input
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
const buttonOne = <button onClick={this.submit}>Submit</button>;
|
||||
const buttonTwo = <button>You May Enter</button>;
|
||||
const buttonThree = <button>You Shall Not Pass</button>;
|
||||
return (
|
||||
<div>
|
||||
<h3>Enter Your Age to Continue</h3>
|
||||
<input
|
||||
style={inputStyle}
|
||||
type='number'
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<br />
|
||||
{/* Change code below this line */}
|
||||
|
||||
{/* Change code above this line */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const inputStyle = {
|
||||
width: 235,
|
||||
margin: 5
|
||||
};
|
||||
|
||||
class CheckUserAge extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
userAge: '',
|
||||
input: ''
|
||||
};
|
||||
this.submit = this.submit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleChange(e) {
|
||||
this.setState({
|
||||
input: e.target.value,
|
||||
userAge: ''
|
||||
});
|
||||
}
|
||||
submit() {
|
||||
this.setState(state => ({
|
||||
userAge: state.input
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
const buttonOne = <button onClick={this.submit}>Submit</button>;
|
||||
const buttonTwo = <button>You May Enter</button>;
|
||||
const buttonThree = <button>You Shall Not Pass</button>;
|
||||
return (
|
||||
<div>
|
||||
<h3>Enter Your Age to Continue</h3>
|
||||
<input
|
||||
style={inputStyle}
|
||||
type='number'
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<br />
|
||||
{this.state.userAge === ''
|
||||
? buttonOne
|
||||
: this.state.userAge >= 18
|
||||
? buttonTwo
|
||||
: buttonThree}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,334 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036183
|
||||
title: 在 React Render 方法中使用 JavaScript
|
||||
challengeType: 6
|
||||
forumTopicId: 301415
|
||||
dashedName: use-advanced-javascript-in-react-render-method
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
在之前的挑戰中,學習瞭如何使用大括號 `{ }` 將 JavaScript 代碼插入到 JSX 代碼中,用於訪問 props、傳遞 props、訪問 state、在代碼中插入註釋以及最近學習的定製組件樣式等任務。 這些都是將 JavaScript 放在 JSX 中的常見用例,但是在 React 組件中使用 JavaScript 代碼還有其他方式。
|
||||
|
||||
在 `render` 方法中編寫 JavaScript,可以把 JavaScript 直接放在 `return` 語句之前,而***不必***將其插入大括號中。 這是因爲它還不在 JSX 代碼中。 如果之後想在 `return` 語句中的 JSX 代碼*裏面*使用變量時,可以將變量名放在大括號中。
|
||||
|
||||
# --instructions--
|
||||
|
||||
在提供的代碼中,`render` 方法中有一個包含 20 個短語的數組,用於表示 20 世紀 80 年代經典魔術八球玩具中的答案。 綁定 `ask` 方法到按鈕的單擊事件,每次單擊該按鈕時,將生成隨機數並將其存儲爲 state 中的 `randomIndex`。 在第 52 行,刪除字符串 `change me!` 並重新分配 `answer` 常量,以便每次組件更新時,代碼隨機訪問 `possibleAnswers` 數組的不同值。 最後,在 `p` 標籤內插入 `answer` 常量。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MagicEightBall` 組件應該存在並被渲染到頁面。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MagicEightBall)).find('MagicEightBall')
|
||||
.length,
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
`MagicEightBall` 的第一個子元素應該是 `input` 元素。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MagicEightBall))
|
||||
.children()
|
||||
.childAt(0)
|
||||
.name(),
|
||||
'input'
|
||||
);
|
||||
```
|
||||
|
||||
`MagicEightBall` 的第三個子元素應該是 `button` 元素。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MagicEightBall))
|
||||
.children()
|
||||
.childAt(2)
|
||||
.name(),
|
||||
'button'
|
||||
);
|
||||
```
|
||||
|
||||
`MagicEightBall` 的 state 應該用 `userInput` 屬性和 `randomIndex` 屬性初始化,並且這兩個屬性的值都應該是空字符串。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MagicEightBall)).state('randomIndex') ===
|
||||
'' &&
|
||||
Enzyme.mount(React.createElement(MagicEightBall)).state('userInput') === ''
|
||||
);
|
||||
```
|
||||
|
||||
當 `MagicEightBall` 第一次加載到 DOM 中時,它應該返回一個空的 `p` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MagicEightBall)).find('p').length === 1 &&
|
||||
Enzyme.mount(React.createElement(MagicEightBall)).find('p').text() === ''
|
||||
);
|
||||
```
|
||||
|
||||
當文本被輸入到 `input` 元素中並點擊按鈕時, `MagicEightBall` 組件應該返回一個 `p` 元素,該元素包含數組 `possibleAnswers` 中的隨機一個元素。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const comp = Enzyme.mount(React.createElement(MagicEightBall));
|
||||
const simulate = () => {
|
||||
comp.find('input').simulate('change', { target: { value: 'test?' } });
|
||||
comp.find('button').simulate('click');
|
||||
};
|
||||
const result = () => comp.find('p').text();
|
||||
const _1 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _2 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _3 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _4 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _5 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _6 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _7 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _8 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _9 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _10 = () => {
|
||||
simulate();
|
||||
return result();
|
||||
};
|
||||
const _1_val = _1();
|
||||
const _2_val = _2();
|
||||
const _3_val = _3();
|
||||
const _4_val = _4();
|
||||
const _5_val = _5();
|
||||
const _6_val = _6();
|
||||
const _7_val = _7();
|
||||
const _8_val = _8();
|
||||
const _9_val = _9();
|
||||
const _10_val = _10();
|
||||
const actualAnswers = [
|
||||
_1_val,
|
||||
_2_val,
|
||||
_3_val,
|
||||
_4_val,
|
||||
_5_val,
|
||||
_6_val,
|
||||
_7_val,
|
||||
_8_val,
|
||||
_9_val,
|
||||
_10_val
|
||||
];
|
||||
const hasIndex = actualAnswers.filter(
|
||||
(answer, i) => possibleAnswers.indexOf(answer) !== -1
|
||||
);
|
||||
const notAllEqual = new Set(actualAnswers);
|
||||
assert(notAllEqual.size > 1 && hasIndex.length === 10);
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
var possibleAnswers = [
|
||||
'It is certain',
|
||||
'It is decidedly so',
|
||||
'Without a doubt',
|
||||
'Yes, definitely',
|
||||
'You may rely on it',
|
||||
'As I see it, yes',
|
||||
'Outlook good',
|
||||
'Yes',
|
||||
'Signs point to yes',
|
||||
'Reply hazy try again',
|
||||
'Ask again later',
|
||||
'Better not tell you now',
|
||||
'Cannot predict now',
|
||||
'Concentrate and ask again',
|
||||
"Don't count on it",
|
||||
'My reply is no',
|
||||
'My sources say no',
|
||||
'Outlook not so good',
|
||||
'Very doubtful',
|
||||
'Most likely'
|
||||
];
|
||||
ReactDOM.render(<MagicEightBall />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const inputStyle = {
|
||||
width: 235,
|
||||
margin: 5
|
||||
};
|
||||
|
||||
class MagicEightBall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
userInput: '',
|
||||
randomIndex: ''
|
||||
};
|
||||
this.ask = this.ask.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
ask() {
|
||||
if (this.state.userInput) {
|
||||
this.setState({
|
||||
randomIndex: Math.floor(Math.random() * 20),
|
||||
userInput: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
userInput: event.target.value
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const possibleAnswers = [
|
||||
'It is certain',
|
||||
'It is decidedly so',
|
||||
'Without a doubt',
|
||||
'Yes, definitely',
|
||||
'You may rely on it',
|
||||
'As I see it, yes',
|
||||
'Outlook good',
|
||||
'Yes',
|
||||
'Signs point to yes',
|
||||
'Reply hazy try again',
|
||||
'Ask again later',
|
||||
'Better not tell you now',
|
||||
'Cannot predict now',
|
||||
'Concentrate and ask again',
|
||||
"Don't count on it",
|
||||
'My reply is no',
|
||||
'My sources say no',
|
||||
'Most likely',
|
||||
'Outlook not so good',
|
||||
'Very doubtful'
|
||||
];
|
||||
const answer = 'change me!'; // Change this line
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type='text'
|
||||
value={this.state.userInput}
|
||||
onChange={this.handleChange}
|
||||
style={inputStyle}
|
||||
/>
|
||||
<br />
|
||||
<button onClick={this.ask}>Ask the Magic Eight Ball!</button>
|
||||
<br />
|
||||
<h3>Answer:</h3>
|
||||
<p>
|
||||
{/* Change code below this line */}
|
||||
|
||||
{/* Change code above this line */}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const inputStyle = {
|
||||
width: 235,
|
||||
margin: 5
|
||||
};
|
||||
|
||||
class MagicEightBall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
userInput: '',
|
||||
randomIndex: ''
|
||||
};
|
||||
this.ask = this.ask.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
ask() {
|
||||
if (this.state.userInput) {
|
||||
this.setState({
|
||||
randomIndex: Math.floor(Math.random() * 20),
|
||||
userInput: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
userInput: event.target.value
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const possibleAnswers = [
|
||||
'It is certain',
|
||||
'It is decidedly so',
|
||||
'Without a doubt',
|
||||
'Yes, definitely',
|
||||
'You may rely on it',
|
||||
'As I see it, yes',
|
||||
'Outlook good',
|
||||
'Yes',
|
||||
'Signs point to yes',
|
||||
'Reply hazy try again',
|
||||
'Ask again later',
|
||||
'Better not tell you now',
|
||||
'Cannot predict now',
|
||||
'Concentrate and ask again',
|
||||
"Don't count on it",
|
||||
'My reply is no',
|
||||
'My sources say no',
|
||||
'Outlook not so good',
|
||||
'Very doubtful',
|
||||
'Most likely'
|
||||
];
|
||||
const answer = possibleAnswers[this.state.randomIndex];
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type='text'
|
||||
value={this.state.userInput}
|
||||
onChange={this.handleChange}
|
||||
style={inputStyle}
|
||||
/>
|
||||
<br />
|
||||
<button onClick={this.ask}>Ask the Magic Eight Ball!</button>
|
||||
<br />
|
||||
<h3>Answer:</h3>
|
||||
<p>{answer}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,236 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403618c
|
||||
title: 使用 Array.Filter() 動態過濾數組
|
||||
challengeType: 6
|
||||
forumTopicId: 301416
|
||||
dashedName: use-array-filter-to-dynamically-filter-an-array
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`map` 數組方法是一個強大的工具,在使用 React 時經常使用。 與 `map` 相關的另一種方法是 `filter`,它根據條件過濾數組的內容,然後返回一個新數組。 例如,如果有一個 users 數組,每個數組元素都有一個可以設置爲 `true` 或 `false` 的 `online` 屬性,可以這樣只過濾那些在線的用戶:
|
||||
|
||||
```js
|
||||
let onlineUsers = users.filter(user => user.online);
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
在代碼編輯器中,`MyComponent` 的 `state` 被初始化爲一個用戶數組。 有些用戶在線,有些則沒有。 過濾數組,以便只查看在線用戶。 要執行此操作,請首先使用 `filter` 返回僅包含 `online` 屬性爲 `true` 的用戶的新數組。 然後,在 `renderOnline` 變量中,映射已過濾的數組,併爲包含其 `username` 文本的每個用戶返回 `li` 元素。 確保包含一個唯一的 `key` ,就像上一個挑戰一樣。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該存在並被渲染到頁面。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MyComponent)).find('MyComponent').length,
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 的 state 應該初始化爲包含 6 個用戶的數組。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Array.isArray(
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('users')
|
||||
) === true &&
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('users').length === 6
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 應該返回一個 `div`、一個 `h1` 元素,和一個包含 `li` 元素無序列表,該列表用於展示在線狀態爲 `true` 的所有用戶。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const comp = Enzyme.mount(React.createElement(MyComponent));
|
||||
const users = (bool) => ({
|
||||
users: [
|
||||
{ username: 'Jeff', online: bool },
|
||||
{ username: 'Alan', online: bool },
|
||||
{ username: 'Mary', online: bool },
|
||||
{ username: 'Jim', online: bool },
|
||||
{ username: 'Laura', online: bool }
|
||||
]
|
||||
});
|
||||
const result = () => comp.find('li').length;
|
||||
const _1 = result();
|
||||
const _2 = () => {
|
||||
comp.setState(users(true));
|
||||
return result();
|
||||
};
|
||||
const _3 = () => {
|
||||
comp.setState(users(false));
|
||||
return result();
|
||||
};
|
||||
const _4 = () => {
|
||||
comp.setState({ users: [] });
|
||||
return result();
|
||||
};
|
||||
const _2_val = _2();
|
||||
const _3_val = _3();
|
||||
const _4_val = _4();
|
||||
assert(
|
||||
comp.find('div').length === 1 &&
|
||||
comp.find('h1').length === 1 &&
|
||||
comp.find('ul').length === 1 &&
|
||||
_1 === 4 &&
|
||||
_2_val === 5 &&
|
||||
_3_val === 0 &&
|
||||
_4_val === 0
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
`MyComponent` 應該渲染包含所有在線用戶的 `username` 的 `li` 元素。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const comp = Enzyme.mount(React.createElement(MyComponent));
|
||||
const users = (bool) => ({
|
||||
users: [
|
||||
{ username: 'Jeff', online: bool },
|
||||
{ username: 'Alan', online: bool },
|
||||
{ username: 'Mary', online: bool },
|
||||
{ username: 'Jim', online: bool },
|
||||
{ username: 'Laura', online: bool }
|
||||
]
|
||||
});
|
||||
const ul = () => {
|
||||
comp.setState(users(true));
|
||||
return comp.find('ul').html();
|
||||
};
|
||||
const html = ul();
|
||||
assert(
|
||||
html ===
|
||||
'<ul><li>Jeff</li><li>Alan</li><li>Mary</li><li>Jim</li><li>Laura</li></ul>'
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
每個列表項元素都應該有一個唯一的 `key` 屬性。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const ul = Enzyme.mount(React.createElement(MyComponent)).find('ul');
|
||||
console.log(ul.debug());
|
||||
const keys = new Set([
|
||||
ul.childAt(0).key(),
|
||||
ul.childAt(1).key(),
|
||||
ul.childAt(2).key(),
|
||||
ul.childAt(3).key()
|
||||
]);
|
||||
return keys.size === 4;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
users: [
|
||||
{
|
||||
username: 'Jeff',
|
||||
online: true
|
||||
},
|
||||
{
|
||||
username: 'Alan',
|
||||
online: false
|
||||
},
|
||||
{
|
||||
username: 'Mary',
|
||||
online: true
|
||||
},
|
||||
{
|
||||
username: 'Jim',
|
||||
online: false
|
||||
},
|
||||
{
|
||||
username: 'Sara',
|
||||
online: true
|
||||
},
|
||||
{
|
||||
username: 'Laura',
|
||||
online: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const usersOnline = null; // Change this line
|
||||
const renderOnline = null; // Change this line
|
||||
return (
|
||||
<div>
|
||||
<h1>Current Online Users:</h1>
|
||||
<ul>{renderOnline}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
users: [
|
||||
{
|
||||
username: 'Jeff',
|
||||
online: true
|
||||
},
|
||||
{
|
||||
username: 'Alan',
|
||||
online: false
|
||||
},
|
||||
{
|
||||
username: 'Mary',
|
||||
online: true
|
||||
},
|
||||
{
|
||||
username: 'Jim',
|
||||
online: false
|
||||
},
|
||||
{
|
||||
username: 'Sara',
|
||||
online: true
|
||||
},
|
||||
{
|
||||
username: 'Laura',
|
||||
online: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const usersOnline = this.state.users.filter(user => {
|
||||
return user.online;
|
||||
});
|
||||
const renderOnline = usersOnline.map(user => {
|
||||
return <li key={user.username}>{user.username}</li>;
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<h1>Current Online Users:</h1>
|
||||
<ul>{renderOnline}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,265 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403618a
|
||||
title: 使用 Array.map() 動態渲染元素
|
||||
challengeType: 6
|
||||
forumTopicId: 301417
|
||||
dashedName: use-array-map-to-dynamically-render-elements
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
條件渲染很有用,但是可能需要組件來渲染未知數量的元素。 通常在響應式編程中,程序員在應用程序運行時之前無法知道其 state,因爲這在很大程度上取決於用戶與該程序的交互。 程序員需要提前編寫代碼來正確處理未知狀態。 在 React 中使用 `Array.map()` 闡明瞭這個概念。
|
||||
|
||||
例如,創建一個簡單的“To Do List”應用程序。 作爲程序員,你無法知道用戶可能在其列表中有多少項。 需要設置組件,以便在使用該程序的人決定今天今日待辦事項之前動態渲染正確數量的列表元素。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器完成了 `MyToDoList` 組件的大部分設置。 如果完成了受控表單挑戰,這些代碼中的一些應該看起來很熟悉。 你會注意到一個 `textarea` 和一個 `button`,以及一些跟蹤它們狀態的方法,但是頁面當前還沒有任何東西被渲染。
|
||||
|
||||
在 `constructor` 中,創建一個 `this.state` 對象並定義兩個 state:`userInput` 應該初始化爲空字符串,`toDoList` 應該初始化爲空數組。 接下來,刪除 `items` 變量旁邊 `render()` 方法中的註釋。 取而代之的是,將存儲在組件內部 state 中的 `toDoList` 數組一一遍歷並相應的動態呈現 `li` 元素中。 嘗試在 `textarea` 中輸入 `eat, code, sleep, repeat`,然後點擊按鈕,看看會發生什麼。
|
||||
|
||||
**注意:** 像這樣的映射操作創建的所有兄弟子元素都需要提供唯一的 `key` 屬性。 別擔心,這是下一個挑戰的主題。
|
||||
|
||||
# --hints--
|
||||
|
||||
MyToDoList 組件應該存在,並渲染到頁面。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
return mockedComponent.find('MyToDoList').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyToDoList` 組件的第一個子元素應該是 `textarea` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
return (
|
||||
mockedComponent.find('MyToDoList').children().childAt(0).type() ===
|
||||
'textarea'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyToDoList` 組件的第二個子元素應該是 `br` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
return (
|
||||
mockedComponent.find('MyToDoList').children().childAt(1).type() === 'br'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyToDoList` 組件的第三個子元素應該是 `button` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
return (
|
||||
mockedComponent.find('MyToDoList').children().childAt(2).type() ===
|
||||
'button'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyToDoList` 的 state 應該使用被設置爲空數組的 `toDoList` 進行初始化。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
const initialState = mockedComponent.state();
|
||||
return (
|
||||
Array.isArray(initialState.toDoList) === true &&
|
||||
initialState.toDoList.length === 0
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyToDoList` 的 state 應該使用被設置爲空字符串的 `userInput` 進行初始化。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
const initialState = mockedComponent.state();
|
||||
return (
|
||||
typeof initialState.userInput === 'string' &&
|
||||
initialState.userInput.length === 0
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
單擊 `Create List` 按鈕時,`MyToDoList` 組件應該動態返回一個無序列表,該列表包含輸入到 `textarea` 元素中的逗號分隔列表的每個項目的列表項目元素。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
const simulateChange = (el, value) =>
|
||||
el.simulate('change', { target: { value } });
|
||||
const state_1 = () => {
|
||||
return mockedComponent.find('ul').find('li');
|
||||
};
|
||||
const setInput = () => {
|
||||
return simulateChange(
|
||||
mockedComponent.find('textarea'),
|
||||
'testA, testB, testC'
|
||||
);
|
||||
};
|
||||
const click = () => {
|
||||
return mockedComponent.find('button').simulate('click');
|
||||
};
|
||||
const state_2 = () => {
|
||||
const nodes = mockedComponent.find('ul').find('li');
|
||||
return { nodes, text: nodes.reduce((t, n) => t + n.text().trim(), '') };
|
||||
};
|
||||
const setInput_2 = () => {
|
||||
return simulateChange(
|
||||
mockedComponent.find('textarea'),
|
||||
't1, t2, t3, t4, t5, t6'
|
||||
);
|
||||
};
|
||||
const click_1 = () => {
|
||||
return mockedComponent.find('button').simulate('click');
|
||||
};
|
||||
const state_3 = () => {
|
||||
const nodes = mockedComponent.find('ul').find('li');
|
||||
return { nodes, text: nodes.reduce((t, n) => t + n.text().trim(), '') };
|
||||
};
|
||||
const awaited_state_1 = state_1();
|
||||
const awaited_setInput = setInput();
|
||||
const awaited_click = click();
|
||||
const awaited_state_2 = state_2();
|
||||
const awaited_setInput_2 = setInput_2();
|
||||
const awaited_click_1 = click_1();
|
||||
const awaited_state_3 = state_3();
|
||||
assert(
|
||||
awaited_state_1.length === 0 &&
|
||||
awaited_state_2.nodes.length === 3 &&
|
||||
awaited_state_3.nodes.length === 6 &&
|
||||
awaited_state_2.text === 'testAtestBtestC' &&
|
||||
awaited_state_3.text === 't1t2t3t4t5t6'
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyToDoList />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const textAreaStyles = {
|
||||
width: 235,
|
||||
margin: 5
|
||||
};
|
||||
|
||||
class MyToDoList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleSubmit() {
|
||||
const itemsArray = this.state.userInput.split(',');
|
||||
this.setState({
|
||||
toDoList: itemsArray
|
||||
});
|
||||
}
|
||||
handleChange(e) {
|
||||
this.setState({
|
||||
userInput: e.target.value
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const items = null; // Change this line
|
||||
return (
|
||||
<div>
|
||||
<textarea
|
||||
onChange={this.handleChange}
|
||||
value={this.state.userInput}
|
||||
style={textAreaStyles}
|
||||
placeholder='Separate Items With Commas'
|
||||
/>
|
||||
<br />
|
||||
<button onClick={this.handleSubmit}>Create List</button>
|
||||
<h1>My "To Do" List:</h1>
|
||||
<ul>{items}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const textAreaStyles = {
|
||||
width: 235,
|
||||
margin: 5
|
||||
};
|
||||
|
||||
class MyToDoList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
toDoList: [],
|
||||
userInput: ''
|
||||
};
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleSubmit() {
|
||||
const itemsArray = this.state.userInput.split(',');
|
||||
this.setState({
|
||||
toDoList: itemsArray
|
||||
});
|
||||
}
|
||||
handleChange(e) {
|
||||
this.setState({
|
||||
userInput: e.target.value
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const items = this.state.toDoList.map((item, i) => {
|
||||
return <li key={i}>{item}</li>;
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<textarea
|
||||
onChange={this.handleChange}
|
||||
value={this.state.userInput}
|
||||
style={textAreaStyles}
|
||||
placeholder='Separate Items With Commas'
|
||||
/>
|
||||
<br />
|
||||
<button onClick={this.handleSubmit}>Create List</button>
|
||||
<h1>My "To Do" List:</h1>
|
||||
<ul>{items}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,78 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403616b
|
||||
title: 使用默認的 Props
|
||||
challengeType: 6
|
||||
forumTopicId: 301418
|
||||
dashedName: use-default-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
React 還有一個設置默認 props 的選項。 可以將默認 props 作爲組件本身的屬性分配給組件,React 會在必要時分配默認 prop。 如果沒有顯式的提供任何值,這允許指定 prop 值應該是什麼。 例如,如果聲明 `MyComponent.defaultProps = { location: 'San Francisco' }`,即定義一個 location 屬性,並且其值在沒有另行制定的情況下被設置爲字符串 `San Francisco`。 如果 props 未定義,則 React 會分配默認 props,但如果你將 `null` 作爲 prop 的值,它將保持 `null`。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器中有一個 `ShoppingCart` 組件。 在這個組件上定義默認 props,它指定一個 `items` prop,其值爲 `0`。
|
||||
|
||||
# --hints--
|
||||
|
||||
應該渲染 `ShoppingCart` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart));
|
||||
return mockedComponent.find('ShoppingCart').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`ShoppingCart` 組件應該有一個 `{ items: 0 }` 的默認 prop。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart));
|
||||
mockedComponent.setProps({ items: undefined });
|
||||
return mockedComponent.find('ShoppingCart').props().items === 0;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<ShoppingCart />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const ShoppingCart = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Shopping Cart Component</h1>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const ShoppingCart = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Shopping Cart Component</h1>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
ShoppingCart.defaultProps = {
|
||||
items: 0
|
||||
}
|
||||
```
|
@ -0,0 +1,132 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403616d
|
||||
title: 使用 PropTypes 來定義 Props 的類型
|
||||
challengeType: 6
|
||||
forumTopicId: 301419
|
||||
dashedName: use-proptypes-to-define-the-props-you-expect
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
React 提供了有用的類型檢查特性,以驗證組件是否接收了正確類型的 props。 例如,應用程序調用 API 來檢索數據是否是數組,然後將數據作爲 prop 傳遞給組件。 可以在組件上設置 `propTypes`,以要求數據的類型爲 `array`。 當數據是任何其它類型時,都會拋出警告。
|
||||
|
||||
當提前知道 prop 的類型時,最佳實踐是設置其 `propTypes`。 可以爲組件定義 `propTypes` 屬性,方法與定義 `defaultProps` 相同。 這樣做將檢查給定鍵的 prop 是否是給定類型。 這裏有一個示例,表示名爲 `handleClick` 的 prop 應爲 `function` 類型:
|
||||
|
||||
```js
|
||||
MyComponent.propTypes = { handleClick: PropTypes.func.isRequired }
|
||||
```
|
||||
|
||||
在上面的示例中,`PropTypes.func` 部分檢查 `handleClick` 是否爲函數。 添加 `isRequired`,告訴 React `handleClick` 是該組件的必需屬性。 如果沒有那個屬性,將出現警告。 還要注意 `func` 代表 `function` 。 在 7 種 JavaScript 原語類型中, `function` 和 `boolean` (寫爲 `bool` )是唯一使用異常拼寫的兩種類型。 除了原始類型,還有其他類型可用。 例如,你可以檢查 prop 是否爲 React 元素。 請參閱[文檔](https://reactjs.org/docs/jsx-in-depth.html#specifying-the-react-element-type)以獲取所有選項。
|
||||
|
||||
**注意:**在 React v15.5.0 中, `PropTypes` 可以從 React 中單獨引入,例如:`import PropTypes from 'prop-types';`。
|
||||
|
||||
# --instructions--
|
||||
|
||||
爲 `Items` 組件定義 `propTypes`,以要求 `quantity` 作爲 prop,並驗證它是否爲 `number` 類型。
|
||||
|
||||
# --hints--
|
||||
|
||||
應該渲染 `ShoppingCart` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart));
|
||||
return mockedComponent.find('ShoppingCart').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應該渲染 `Items` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart));
|
||||
return mockedComponent.find('Items').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Items` 組件應該包含一個 `propTypes`,要求 `quantity` 有一個 number 類型的值。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
const noWhiteSpace = __helpers.removeWhiteSpace(getUserInput('index'));
|
||||
return (
|
||||
noWhiteSpace.includes('quantity:PropTypes.number.isRequired') &&
|
||||
noWhiteSpace.includes('Items.propTypes=')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --before-user-code--
|
||||
|
||||
```jsx
|
||||
var PropTypes = {
|
||||
number: { isRequired: true }
|
||||
};
|
||||
```
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<ShoppingCart />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const Items = (props) => {
|
||||
return <h1>Current Quantity of Items in Cart: {props.quantity}</h1>
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
|
||||
Items.defaultProps = {
|
||||
quantity: 0
|
||||
};
|
||||
|
||||
class ShoppingCart extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <Items />
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const Items = (props) => {
|
||||
return <h1>Current Quantity of Items in Cart: {props.quantity}</h1>
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
Items.propTypes = {
|
||||
quantity: PropTypes.number.isRequired
|
||||
};
|
||||
// Change code above this line
|
||||
|
||||
Items.defaultProps = {
|
||||
quantity: 0
|
||||
};
|
||||
|
||||
class ShoppingCart extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <Items />
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,150 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036165
|
||||
title: 使用 React 渲染嵌套組件
|
||||
challengeType: 6
|
||||
forumTopicId: 301420
|
||||
dashedName: use-react-to-render-nested-components
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
上一個挑戰顯示了組合兩個組件的簡單方法,但是有許多不同的方法可以把 React 組件組合在一起。
|
||||
|
||||
組件組合是 React 的強大功能之一。 當使用 React 時,應當先用組件的思路考慮清楚用戶界面的結構(如上一個挑戰中的 App 示例)。 可以將 UI 分解爲基本的構建塊,這些構建塊就是組件。 這樣做有助於將負責 UI 的代碼與負責處理應用程序邏輯的代碼分開, 並可以大大簡化複雜項目的開發和維護。
|
||||
|
||||
# --instructions--
|
||||
|
||||
代碼編輯器中定義了兩個功能組件,分別是 `TypesOfFruit` 和 `Fruits`。 請用組合或者*嵌套*把 `TypesOfFruit` 組件放到 `Fruits` 組件中, 然後把 `Fruits` 組件放到 `TypesOfFood` 組件中。 結果應該是子組件嵌套在父組件中,父組件嵌套在它本身的父組件中!
|
||||
|
||||
# --hints--
|
||||
|
||||
`TypesOfFood` 組件應該返回單個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(Enzyme.shallow(React.createElement(TypesOfFood)).type() === 'div');
|
||||
```
|
||||
|
||||
`TypesOfFood` 組件應該返回 `Fruits` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.shallow(React.createElement(TypesOfFood)).props().children[1].type
|
||||
.name === 'Fruits'
|
||||
);
|
||||
```
|
||||
|
||||
`Fruits` 組件應該返回 `TypesOfFruit` 組件。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(TypesOfFood)).find('h2').html() ===
|
||||
'<h2>Fruits:</h2>'
|
||||
);
|
||||
```
|
||||
|
||||
`TypesOfFruit` 組件應該返回 `h2` 和 `ul` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(TypesOfFood)).find('ul').text() ===
|
||||
'ApplesBlueberriesStrawberriesBananas'
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<TypesOfFood />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const TypesOfFruit = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Fruits:</h2>
|
||||
<ul>
|
||||
<li>Apples</li>
|
||||
<li>Blueberries</li>
|
||||
<li>Strawberries</li>
|
||||
<li>Bananas</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Fruits = () => {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class TypesOfFood extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Types of Food:</h1>
|
||||
{ /* Change code below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const TypesOfFruit = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Fruits:</h2>
|
||||
<ul>
|
||||
<li>Apples</li>
|
||||
<li>Blueberries</li>
|
||||
<li>Strawberries</li>
|
||||
<li>Bananas</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Fruits = () => {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<TypesOfFruit />
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class TypesOfFood extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Types of Food:</h1>
|
||||
{ /* Change code below this line */ }
|
||||
<Fruits />
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,189 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036176
|
||||
title: 使用 State 切換元素
|
||||
challengeType: 6
|
||||
forumTopicId: 301421
|
||||
dashedName: use-state-to-toggle-an-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
有時可能在更新狀態的時候想知道上一個狀態是什麼。 但是狀態更新是異步的,這意味着 React 可能會把多個 `setState()` 集中在一起批量更新。 所以計算下一個值時 `this.state` 或者 `this.props` 不能作爲當前值。 所以最好不要寫如下的代碼:
|
||||
|
||||
```jsx
|
||||
this.setState({
|
||||
counter: this.state.counter + this.props.increment
|
||||
});
|
||||
```
|
||||
|
||||
正確的做法是,給 `setState` 傳入一個函數,這個函數可以訪問 state 和 props。 給 `setState` 傳入函數可以保證 state 和 props 是正確的值。 代碼可以重寫爲這樣:
|
||||
|
||||
```jsx
|
||||
this.setState((state, props) => ({
|
||||
counter: state.counter + props.increment
|
||||
}));
|
||||
```
|
||||
|
||||
如果只需要 `state`,那麼用下面沒有 `props` 的格式也是可以的:
|
||||
|
||||
```jsx
|
||||
this.setState(state => ({
|
||||
counter: state.counter + 1
|
||||
}));
|
||||
```
|
||||
|
||||
注意一定要把 object 放在括號裏,否則 JavaScript 會認爲這只是代碼片段。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`MyComponent` 有一個初始值爲 `false` 的`visibility` 屬性。 如果 `visibility` 的值爲 true,render 方法返回一個視圖,如果爲 false,返回另一個視圖。
|
||||
|
||||
目前,無法更新組件 `state` 中的 `visibility` 屬性, 該值應在 true 和 false 之間來回切換。 按鈕上有一個單擊處理程序,它觸發一個名爲 `toggleVisibility()` 的類方法。 給函數傳入 `setState` 來定義此方法,以便 `visibility` 的 `state` 在調用方法時切換到相反的值。 如果 `visibility` 是 `false`,則該方法將其設置爲`true`,反之亦然。
|
||||
|
||||
最後,單擊按鈕以查看基於其 `state` 的組件的條件渲染。
|
||||
|
||||
**提示:** 不要忘記將 `this` 關鍵字綁定到 `constructor` 中的方法上!
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該返回一個 `div` 元素,其中包含一個 `button` 元素。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MyComponent)).find('div').find('button')
|
||||
.length,
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 應該使用設置爲 `false` 的 `visibility` 屬性來初始化其 state。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('visibility'),
|
||||
false
|
||||
);
|
||||
```
|
||||
|
||||
單擊按鈕元素應在 `true` 和 `false` 之間切換 `visibility` 屬性的狀態。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const first = () => {
|
||||
mockedComponent.setState({ visibility: false });
|
||||
return mockedComponent.state('visibility');
|
||||
};
|
||||
const second = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return mockedComponent.state('visibility');
|
||||
};
|
||||
const third = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return mockedComponent.state('visibility');
|
||||
};
|
||||
const firstValue = first();
|
||||
const secondValue = second();
|
||||
const thirdValue = third();
|
||||
assert(!firstValue && secondValue && !thirdValue);
|
||||
})();
|
||||
```
|
||||
|
||||
應該給 `setState` 傳入一個匿名函數。
|
||||
|
||||
```js
|
||||
const paramRegex = '[a-zA-Z$_]\\w*(,[a-zA-Z$_]\\w*)?';
|
||||
assert(
|
||||
new RegExp(
|
||||
'this\\.setState\\((function\\(' +
|
||||
paramRegex +
|
||||
'\\){|([a-zA-Z$_]\\w*|\\(' +
|
||||
paramRegex +
|
||||
'\\))=>)'
|
||||
).test(__helpers.removeWhiteSpace(code))
|
||||
);
|
||||
```
|
||||
|
||||
不要在 `setState` 裏面使用 `this`。
|
||||
|
||||
```js
|
||||
assert(!/this\.setState\([^}]*this/.test(code));
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
visibility: false
|
||||
};
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
}
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
render() {
|
||||
if (this.state.visibility) {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.toggleVisibility}>Click Me</button>
|
||||
<h1>Now you see me!</h1>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.toggleVisibility}>Click Me</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
visibility: false
|
||||
};
|
||||
this.toggleVisibility = this.toggleVisibility.bind(this);
|
||||
}
|
||||
toggleVisibility() {
|
||||
this.setState(state => ({
|
||||
visibility: !state.visibility
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
if (this.state.visibility) {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.toggleVisibility}>Click Me</button>
|
||||
<h1>Now you see me!</h1>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.toggleVisibility}>Click Me</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,127 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403617d
|
||||
title: 使用生命週期方法:componentDidMount
|
||||
challengeType: 6
|
||||
forumTopicId: 301422
|
||||
dashedName: use-the-lifecycle-method-componentdidmount
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
某些時候,大多數 web 開發人員需要調用 API 接口來獲取數據。 如果正在使用 React,知道在哪裏執行這個動作是很重要的。
|
||||
|
||||
React 的最佳實踐是在生命週期方法 `componentDidMount()` 中對服務器進行 API 調用或任何其它調用。 將組件裝載到 DOM 後會調用此方法。 此處對 `setState()` 的任何調用都將觸發組件的重新渲染。 在此方法中調用 API 並用 API 返回的數據設置 state 時,一旦收到數據,它將自動觸發更新。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`componentDidMount()` 中有一個模擬 API 調用。 它在 2.5 秒後設置 state,以模擬調用服務器檢索數據。 本示例請求站點的當前活動用戶總數。 在 render 方法中,把 `activeUsers` 渲染到文字 `Active Users:` 後的 `h1` 標籤中。 觀看預覽中發生的事情,隨意更改超時時間以查看不同的效果。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該渲染一個包含 `h1` 標籤的 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return (
|
||||
mockedComponent.find('div').length === 1 &&
|
||||
mockedComponent.find('h1').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
組件 state 應該用 `componentDidMount` 中的延時函數來更新。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return new RegExp('setTimeout(.|\n)+setState(.|\n)+activeUsers').test(
|
||||
String(mockedComponent.instance().componentDidMount)
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`h1` 標籤應該從 `MyComponent` 的 state 渲染 `activeUsers` 值。
|
||||
|
||||
```js
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
const first = () => {
|
||||
mockedComponent.setState({ activeUsers: 1237 });
|
||||
return mockedComponent.find('h1').text();
|
||||
};
|
||||
const second = () => {
|
||||
mockedComponent.setState({ activeUsers: 1000 });
|
||||
return mockedComponent.find('h1').text();
|
||||
};
|
||||
assert(new RegExp('1237').test(first()) && new RegExp('1000').test(second()));
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeUsers: null
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
activeUsers: 1273
|
||||
});
|
||||
}, 2500);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{/* Change code below this line */}
|
||||
<h1>Active Users: </h1>
|
||||
{/* Change code above this line */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeUsers: null
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
activeUsers: 1273
|
||||
});
|
||||
}, 2500);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Active Users: {this.state.activeUsers}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,87 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403617c
|
||||
title: 使用生命週期方法:componentWillMount
|
||||
challengeType: 6
|
||||
forumTopicId: 301423
|
||||
dashedName: use-the-lifecycle-method-componentwillmount
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
React 組件有幾種特殊方法,可以在組件生命週期的特定點執行操作。 這些稱爲生命週期方法或生命週期鉤子,允許在特定時間點捕獲組件。 這可以在渲染之前、更新之前、接收 props 之前、卸載之前等等。 以下是一些主要生命週期方法的列表: `componentWillMount()` `componentDidMount()` `shouldComponentUpdate()` `componentDidUpdate()` `componentWillUnmount()` 接下來的幾節課將講述這些生命週期方法的一些基本用例。
|
||||
|
||||
**注意:** `componentWillMount` 生命週期方法會在版本 16.X 廢棄在版本 17 移除。 [(來源)](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html)
|
||||
|
||||
# --instructions--
|
||||
|
||||
當組件被掛載到 DOM 時,`componentWillMount()` 方法在 `render()` 方法之前被調用。 在`componentWillMount()`中將一些內容記錄到控制檯 -- 可能需要打開瀏覽器控制檯以查看輸出。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` 應該渲染一個 `div` 元素。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return mockedComponent.find('div').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
應該在 `componentWillMount` 中調用 `console.log`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const lifecycle = React.createElement(MyComponent)
|
||||
.type.prototype.componentWillMount.toString()
|
||||
.replace(/ /g, '');
|
||||
return lifecycle.includes('console.log(');
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
componentWillMount() {
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
}
|
||||
render() {
|
||||
return <div />
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
componentWillMount() {
|
||||
// Change code below this line
|
||||
console.log('Component is mounting...');
|
||||
// Change code above this line
|
||||
}
|
||||
render() {
|
||||
return <div />
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,84 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036168
|
||||
title: 從零開始寫一個 React 組件
|
||||
challengeType: 6
|
||||
forumTopicId: 301424
|
||||
dashedName: write-a-react-component-from-scratch
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
你已經瞭解了 JSX 和 React 組件的基礎知識,是時候自己編寫一個組件了。 React 組件是 React 應用程序的核心組成部分,因此熟練編寫它們是非常重要的。 記住,典型的 React 組件是 ES6 `class`,它擴展了 `React.Component`。 它有一個返回 HTML(從 JSX 返回)或 `null` 的渲染方法, 這是 React 組件的基本形式。 理解了這一點之後,就可以開始構建更復雜的 React 項目了。
|
||||
|
||||
# --instructions--
|
||||
|
||||
定義一個 `MyComponent` 類,它是 `React.Component` 的擴展。 它的渲染方法應該返回一個 `div`,其中包含一個 `h1` 標籤,標籤文本爲:`My First React Component!`。 準確使用此文本,大小寫和標點符號也要考慮。 確保也調用組件的構造器。
|
||||
|
||||
使用 `ReactDOM.render()` 把該組件渲染到 DOM 中。 有一個 `id='challenge-node'` 的 `div` 可供渲染。
|
||||
|
||||
# --hints--
|
||||
|
||||
應該有一個名爲 `MyComponent` 的 React 組件。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
__helpers
|
||||
.removeWhiteSpace(getUserInput('index'))
|
||||
.includes('classMyComponentextendsReact.Component{')
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 應該包含一個 `h1` 標籤,標籤的文本爲 `My First React Component!`,區分大小寫並有標點符號。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
return mockedComponent.find('h1').text() === 'My First React Component!';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` 應該渲染到 DOM。
|
||||
|
||||
```js
|
||||
assert(document.getElementById('challenge-node').childNodes.length === 1);
|
||||
```
|
||||
|
||||
`MyComponent` 應該有一個構造器,裏面調用了傳參 `props` 的 `super` 函數。
|
||||
|
||||
```js
|
||||
assert(
|
||||
MyComponent.toString().includes('MyComponent(props)') &&
|
||||
MyComponent.toString().includes('_super.call(this, props)')
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
// Change code below this line
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>My First React Component!</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ReactDOM.render(<MyComponent />, document.getElementById('challenge-node'));
|
||||
```
|
@ -0,0 +1,146 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036177
|
||||
title: 寫一個簡單的計數器
|
||||
challengeType: 6
|
||||
forumTopicId: 301425
|
||||
dashedName: write-a-simple-counter
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
可以結合目前爲止所涵蓋的概念來設計更復雜的有狀態組件。 這包括初始化 `state`,編寫設置 `state` 的方法,以及指定單擊處理程序來觸發這些方法。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`Counter` 組件跟蹤 `state` 中的 `count` 值。 有兩個按鈕分別調用 `increment()` 和 `decrement()` 方法。 編寫這些方法,使計數器值在單擊相應按鈕時增加或減少 1。 另外,創建一個 `reset()` 方法,當單擊 reset 按鈕時,把計數設置爲 0。
|
||||
|
||||
**注意:** 確保沒有修改按鈕的 `className`。 另外,請記住在構造函數中爲新創建的方法添加必要的綁定。
|
||||
|
||||
# --hints--
|
||||
|
||||
`Counter` 應該返回一個 `div` 元素,它包含三個按鈕,按鈕內容依次是 `Increment!`、`Decrement!`、`Reset`。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Counter));
|
||||
return (
|
||||
mockedComponent.find('.inc').text() === 'Increment!' &&
|
||||
mockedComponent.find('.dec').text() === 'Decrement!' &&
|
||||
mockedComponent.find('.reset').text() === 'Reset'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Counter` 應該使用設置爲 `0` 的 `count` 屬性初始化 state。
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Counter));
|
||||
assert(mockedComponent.find('h1').text() === 'Current Count: 0');
|
||||
```
|
||||
|
||||
單擊 increment 按鈕應將計數增加 `1`。
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Counter));
|
||||
mockedComponent.find('.inc').simulate('click');
|
||||
assert(mockedComponent.find('h1').text() === 'Current Count: 1');
|
||||
```
|
||||
|
||||
單擊 decrement 按鈕應將計數減少 `1`。
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Counter));
|
||||
mockedComponent.find('.dec').simulate('click');
|
||||
assert(mockedComponent.find('h1').text() === 'Current Count: -1');
|
||||
```
|
||||
|
||||
單擊 reset 按鈕應將計數重置爲 `0`。
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Counter));
|
||||
mockedComponent.setState({ count: 5 });
|
||||
const currentCountElement = mockedComponent.find('h1');
|
||||
assert(currentCountElement.text() === 'Current Count: 5');
|
||||
mockedComponent.find('.reset').simulate('click');
|
||||
assert(currentCountElement.text() === 'Current Count: 0');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<Counter />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class Counter extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
count: 0
|
||||
};
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
}
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button className='inc' onClick={this.increment}>Increment!</button>
|
||||
<button className='dec' onClick={this.decrement}>Decrement!</button>
|
||||
<button className='reset' onClick={this.reset}>Reset</button>
|
||||
<h1>Current Count: {this.state.count}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class Counter extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
count: 0
|
||||
};
|
||||
this.increment = this.increment.bind(this);
|
||||
this.decrement = this.decrement.bind(this);
|
||||
this.reset = this.reset.bind(this);
|
||||
}
|
||||
reset() {
|
||||
this.setState({
|
||||
count: 0
|
||||
});
|
||||
}
|
||||
increment() {
|
||||
this.setState(state => ({
|
||||
count: state.count + 1
|
||||
}));
|
||||
}
|
||||
decrement() {
|
||||
this.setState(state => ({
|
||||
count: state.count - 1
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button className='inc' onClick={this.increment}>Increment!</button>
|
||||
<button className='dec' onClick={this.decrement}>Decrement!</button>
|
||||
<button className='reset' onClick={this.reset}>Reset</button>
|
||||
<h1>Current Count: {this.state.count}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
Reference in New Issue
Block a user