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