chore(i18n,learn): processed translations (#44851)
This commit is contained in:
@ -0,0 +1,148 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403616e
|
||||
title: this.props を使用して props にアクセスする
|
||||
challengeType: 6
|
||||
forumTopicId: 301375
|
||||
dashedName: access-props-using-this-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
前のいくつかのチャレンジでは、子コンポーネントに props を渡す基本的な方法について説明しました。 しかし、props に渡そうとしている子コンポーネントが、ステートレス関数型コンポーネントではなく、ES6 クラスコンポーネントである場合はどうでしょうか? ES6 クラスコンポーネントでは、props にアクセスするために多少異なる規約を使用します。
|
||||
|
||||
クラスコンポーネント自体を参照するときはいつでも、`this` キーワードを使用します。 クラスコンポーネント内の props にアクセスするには、アクセスに使用するコードの前に `this` を置きます。 たとえば、ES6 クラスコンポーネントに `data` という prop がある場合、JSX では `{this.props.data}` と記述します。
|
||||
|
||||
# --instructions--
|
||||
|
||||
親コンポーネント `App` にある `Welcome` コンポーネントのインスタンスをレンダーしてください。 ここでは、`Welcome` に `name` という prop を付け、文字列の値を割り当ててください。 子要素 `Welcome` の中で、`strong` タグ内の `name` prop にアクセスしてください。
|
||||
|
||||
# --hints--
|
||||
|
||||
`App` コンポーネントは単一の `div` 要素を返す必要があります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(App));
|
||||
return mockedComponent.children().type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`App` の子要素は `Welcome` コンポーネントである必要があります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(App));
|
||||
return (
|
||||
mockedComponent.children().childAt(0).name() === 'Welcome'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Welcome` コンポーネントには `name` という prop を持たせる必要があります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(App));
|
||||
return mockedComponent.find('Welcome').props().name;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Welcome` コンポーネントで、`name` prop として渡した文字列を `strong` タグ内に表示する必要があります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(App));
|
||||
return (
|
||||
mockedComponent.find('strong').text() ===
|
||||
mockedComponent.find('Welcome').props().name
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --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 */ }
|
||||
<Welcome />
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class Welcome extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<p>Hello, <strong></strong>!</p>
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class Welcome extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<p>Hello, <strong>{this.props.name}</strong>!</p>
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{ /* Change code below this line */ }
|
||||
<Welcome name="Quincy"/>
|
||||
{ /* 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 要素があります。 既存の `h1` または `p` 要素を変更せずに、指定された `div` 要素内のどこかにコメントを追加してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
定数 `JSX` は `div` 要素を返す必要があります。
|
||||
|
||||
```js
|
||||
assert(JSX.type === 'div');
|
||||
```
|
||||
|
||||
`div` には最初の要素として `h1` タグを含める必要があります。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[0].type === 'h1');
|
||||
```
|
||||
|
||||
`div` には 2 番目の要素として `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--
|
||||
|
||||
`keydown` イベントの `componentDidMount()` メソッドでイベントリスナーをアタッチし、これらのイベントでコールバックの `handleKeyPress()` をトリガーしてください。 `document.addEventListener()` を使用して、1 つ目の引数としてイベント (引用符で囲みます) を受け取り、2 つ目の引数としてコールバックを受け取ることができます。
|
||||
|
||||
次に、`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` リスナーをドキュメントにアタッチします。
|
||||
|
||||
```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` で `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 のインラインスタイルとの構文の違いがいくつかありました。 まず、特定の CSS スタイルプロパティの名前にキャメルケースを使用しています。 たとえば前回のチャレンジでは、フォントのサイズを設定するのに `font-size` ではなく `fontSize` を使用しました。 `font-size` のようなハイフンつなぎの単語は JavaScript オブジェクトのプロパティの構文としては無効であるため、React ではキャメルケースを使用しています。 通常、JSX ではハイフンつなぎのスタイルプロパティはキャメルケースを使用して記述します。
|
||||
|
||||
プロパティ値 (`height`、`width`、`fontSize` など) の長さの単位はすべて、特に指定がない限り `px` とみなされます。 たとえば `em` を使用する場合は、`{fontSize: "4em"}` のように値と単位を引用符で囲みます。 デフォルトで `px` になる長さの値を除いて、それ以外の他のすべてのプロパティ値は引用符で囲む必要があります。
|
||||
|
||||
# --instructions--
|
||||
|
||||
スタイルセットがたくさんある場合、スタイル `object` を定数に割り当てることでコードを整理することができます。 ファイルの先頭で、styles 定数をグローバル変数として宣言してください。 `styles` 定数を初期化し、3 つのスタイルプロパティとそれらの値を持つ `object` を割り当ててください。 `div` の color を `purple` に、font-size を `40` に、border を `2px solid purple` に設定してください。 そして、`style` 属性を `styles` 定数と等しく設定してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
`styles` 変数を、3 つのプロパティを持つ `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' をバインドする
|
||||
challengeType: 6
|
||||
forumTopicId: 301379
|
||||
dashedName: bind-this-to-a-class-method
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`state` の設定と更新に加えて、コンポーネントクラスのメソッドを定義することもできます。 クラスメソッドでは通常、メソッドのスコープ内でクラスのプロパティ (`state` や `props` など) にアクセスできるように、`this` キーワードを使用する必要があります。 いくつかの方法でクラスメソッドから `this` にアクセスすることができます。
|
||||
|
||||
よく使用される方法として、コンストラクターで `this` を明示的にバインドすることができます。この場合、コンポーネントの初期化時に `this` がクラスメソッドにバインドされます。 気づいたかもしれませんが、前回のチャレンジでは `this.handleClick = this.handleClick.bind(this)` をコンストラクターの `handleClick` メソッドに使用しました。 その後、クラスメソッドの中で `this.setState()` のように関数を呼び出すと、`this` はクラスを参照し、`undefined` ではなくなります。
|
||||
|
||||
**注:** `this` キーワードは JavaScript で特に混乱を招くものの一つですが、React では重要な役割を果たしています。 ここでの動作はまったくふつうですが、今回のレッスンでは `this` についての詳しい復習はしません。詳細な説明が必要な方は他のレッスンを参照してください。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに、テキストを追跡する `state` を持つコンポーネントがあります。 また、テキストを `You clicked!` に設定できるメソッドがあります。 ただし、使用している `this` キーワードが未定義であるため、このメソッドは動作しません。 コンポーネントのコンストラクターで `this` を `handleClick()` メソッドに明示的にバインドして修正してください。
|
||||
|
||||
次に、render メソッドの `button` 要素に click ハンドラーを追加してください。 このハンドラーでは、ボタンが click イベントを受け取ったときに `handleClick()` メソッドをトリガーする必要があります。 `onClick` ハンドラーに渡すメソッドには中括弧を付けて、JavaScript として直接解釈されるようにする必要があることに注意してください。
|
||||
|
||||
上記の手順を完了すると、ボタンをクリックしたときに `You clicked!` と表示することができます。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` から、ボタンと `h1` 要素の 2 つの要素をこの順序で囲む `div` 要素を返します。
|
||||
|
||||
```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'
|
||||
);
|
||||
```
|
||||
|
||||
キーと値のペア `{ text: "Hello" }` を使用して `MyComponent` の state を初期化します。
|
||||
|
||||
```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 コンポーネントの状態に基づいて条件付きで CSS をレンダーすることもできます。 それには、条件をチェックし、条件が満たされている場合は、render メソッド内の JSX 要素に割り当てられているスタイルオブジェクトを変更します。
|
||||
|
||||
この方法は、DOM 要素を直接変更してスタイルを適用するという従来のアプローチと比べて、かなり大きな変更となるため、理解することが重要です (たとえば jQuery ではごく一般的です)。 このアプローチでは、要素がいつ変更されたのかを追跡する必要があり、実際の操作を直接処理する必要もあります。 変更の追跡が困難になり、UI が予測できなくなる可能性があります。 条件に基づいてスタイルオブジェクトを設定するときは、UI をアプリケーションの状態の関数としてどのように表示するかを記述します。 情報の流れは明確で、一方向にしか流れません。 React でアプリケーションを記述するときは、こうした方法が適切です。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに、スタイル付きの境界線を持つ、シンプルな制御された入力コンポーネントがあります。 ユーザーが入力ボックスに 15 文字を超えるテキストを入力した場合に、この境界線を赤色に変更する必要があります。 このことをチェックする条件を追加し、条件が有効な場合に入力の境界線のスタイルを `3px solid red` に設定してください。 入力欄にテキストを入力して試すことができます。
|
||||
|
||||
# --hints--
|
||||
|
||||
`GateKeeper` コンポーネントで `div` 要素をレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(GateKeeper));
|
||||
return mockedComponent.find('div').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`GateKeeper` コンポーネントを初期化し、状態のキー `input` に空文字列を設定します。
|
||||
|
||||
```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'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
状態の入力値が 15 文字を超える場合は、`input` タグの境界線のスタイルを `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` コンポーネントがあります。
|
||||
|
||||
2 つのコンポーネントを `Fruits` の中に入れてください。最初に `NonCitrus` に、次に `Citrus` に入れてください。 これらのコンポーネントはどちらもあらかじめ用意されています。 次に、`Fruits` クラスコンポーネントを `TypesOfFood` の `h1` 見出し要素の下、`Vegetables` の上に入れてください。 この結果、2 つの異なるコンポーネントタイプを使用する、一連のネストされたコンポーネントができます。
|
||||
|
||||
# --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` コンポーネントから、`Fruits` コンポーネントの下に `Vegetables` コンポーネントを返します。
|
||||
|
||||
```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 の単純な例でしたが、もっと複雑な HTML を JSX で表現することもできます。
|
||||
|
||||
ネストされた JSX について一つ重要なのは、単一の要素を返さなければならないということです。
|
||||
|
||||
この 1 つの親要素で、他のすべてのレベルのネストされた要素を囲むことになります。
|
||||
|
||||
たとえば、親ラッパー要素を持たない兄弟要素として記述された複数の 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--
|
||||
|
||||
次の要素を出現順に含む `div` をレンダーする、新しい定数 `JSX` を定義してください。
|
||||
|
||||
`h1`、`p`、および 3 つの `li` アイテムを含む順序なしリスト。 要素にはそれぞれ任意のテキストを含めることができます。
|
||||
|
||||
**注:** こうした複数の要素をレンダーする場合、すべてを括弧で囲むことができますが、厳密に必要というわけではありません。 また、このチャレンジでは `div` タグを使用して、単一の親要素の中ですべての子要素を囲んでいます。 `div` を削除すると、JSX はトランスパイルを実行しなくなります。 このことは React コンポーネントで JSX 要素を返す場合にも適用されるので、覚えておいてください。
|
||||
|
||||
# --hints--
|
||||
|
||||
定数 `JSX` から `div` 要素を返します。
|
||||
|
||||
```js
|
||||
assert(JSX.type === 'div');
|
||||
```
|
||||
|
||||
`div` に 1 つ目の要素として `h1` タグを含めます。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[0].type === 'h1');
|
||||
```
|
||||
|
||||
`div` に 2 つ目の要素として `p` タグを含めます。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[1].type === 'p');
|
||||
```
|
||||
|
||||
`div` に 3 つ目の要素として `ul` タグを含めます。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[2].type === 'ul');
|
||||
```
|
||||
|
||||
`ul` に `li` 要素を 3 つ含めます。
|
||||
|
||||
```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: コンポジションを使用してコンポーネントを作成する
|
||||
challengeType: 6
|
||||
forumTopicId: 301383
|
||||
dashedName: create-a-component-with-composition
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
ここでは、複数の React コンポーネントをまとめて作成する方法について説明します。 アプリを作成していて、すでに `Navbar`、`Dashboard`、`Footer` の 3 つのコンポーネントを作成したとします。
|
||||
|
||||
これら 3 つのコンポーネントを*子*としてレンダーする `App` という*親*コンポーネントを作成することができます。 コンポーネントを React コンポーネント内の子としてレンダーするには、カスタム HTML タグとして記述したコンポーネント名を JSX に含めます。 たとえば、`render` メソッドで次のように記述できます。
|
||||
|
||||
```jsx
|
||||
return (
|
||||
<App>
|
||||
<Navbar />
|
||||
<Dashboard />
|
||||
<Footer />
|
||||
</App>
|
||||
)
|
||||
```
|
||||
|
||||
React で、別のコンポーネントを参照するカスタム HTML タグが出現すると (この例では `< />` で囲まれたコンポーネント名)、そのコンポーネントのマークアップがタグの場所にレンダーされます。 これにより、`App` コンポーネントと `Navbar`、`Dashboard`、`Footer` との間の親子関係が示されます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに、`ChildComponent` という単純な関数型コンポーネントと、`ParentComponent` というクラスコンポーネントがあります。 `ParentComponent` の中に `ChildComponent` をレンダーして、2 つのコンポーネントをまとめて作成してください。 必ずフォワードスラッシュで `ChildComponent` タグを終了してください。
|
||||
|
||||
**注:** `ChildComponent` は ES6 のアロー関数で定義されています。これは React を使用するときのごく一般的な記法です。 ただし、これは単なる関数にすぎません。 アロー関数の構文に慣れていない方は、JavaScript のセクションを参照してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
React コンポーネントから単一の `div` 要素を返します。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
var shallowRender = Enzyme.shallow(React.createElement(ParentComponent));
|
||||
return shallowRender.type() === 'div';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
コンポーネントから 2 つのネストされた要素を返します。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
var shallowRender = Enzyme.shallow(React.createElement(ParentComponent));
|
||||
return shallowRender.children().length === 2;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
コンポーネントから `ChildComponent` を 2 番目の子として返します。
|
||||
|
||||
```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 では `input` や `textarea` などの特定の要素について、その内部状態を制御できることを示しました。これらは制御されたコンポーネントになります。 このことは、通常の HTML `form` 要素を含む他のフォーム要素にも適用されます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
submit ハンドラーを持つ空の `form` で設定された `MyForm` コンポーネントがあります。 submit ハンドラーはフォームの送信時に呼び出されます。
|
||||
|
||||
フォームを送信するボタンはすでに追加してあります。 その `type` は `submit` に設定されていて、フォームを制御するボタンであることを示しています。 `form` に `input` 要素を追加し、前回のチャレンジのように、その `value` と `onChange()` 属性を設定してください。 次に、`handleSubmit` メソッドで、コンポーネントの state プロパティ `submit` をローカルの `state` の現在の入力値に設定して、メソッドを完成させてください。
|
||||
|
||||
**注:** submit ハンドラーでは、デフォルトのフォーム送信動作が実行されてウェブページが更新されるのを防ぐために、`event.preventDefault()` を呼び出す必要もあります。 ここでは簡単のため、更新によるチャレンジコードのリセットを防ぐために、デフォルトの動作は無効になっています。
|
||||
|
||||
さらに、`form` の後に、コンポーネントの `state` の `submit` 値をレンダーする `h1` タグを作成してください。 これで、フォームに入力してボタンをクリックすると (または 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--
|
||||
|
||||
アプリケーションによっては、レンダーされた UI と `state` との間でもっと複雑なやり取りをする場合があります。 たとえば、`input` や `textarea` などのテキスト入力のフォームコントロール要素は、DOM 内ではユーザータイプとして独自の状態を維持します。 React では、こうしたミュータブルな状態の扱いを React コンポーネントの `state` に移すことができます。 ユーザーの入力はアプリケーションの `state` の一部となり、その入力フィールドの値は React によって制御されます。 通常は、ユーザー入力が可能な入力フィールドを持つ React コンポーネントがある場合、それは制御された入力フォームになります。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに、制御された `input` 要素を作成するための `ControlledInput` というコンポーネントのスケルトンがあります。 コンポーネントの `state` は、空文字列を保持する `input` プロパティですでに初期化されています。 この値は、ユーザーが `input` フィールドに入力するテキストを表します。
|
||||
|
||||
まず、`event` というパラメーターを持つ `handleChange()` というメソッドを作成してください。 このメソッドが呼び出されると、`input` 要素からのテキスト文字列を含む `event` オブジェクトを受け取ります。 この文字列にはメソッドの中で `event.target.value` を使用してアクセスできます。 コンポーネントの `state` の `input` プロパティを、この新しい文字列に更新してください。
|
||||
|
||||
`render` メソッドで、`h4` タグの上に `input` 要素を作成してください。 コンポーネントの `state` の `input` プロパティと等しい `value` 属性を追加してください。 そして、`onChange()` イベントハンドラーを追加して `handleChange()` メソッドに設定してください。
|
||||
|
||||
入力ボックスに入力すると、そのテキストは、ローカルの `state` の `input` プロパティとして設定された `handleChange()` メソッドによって処理され、ページ上の `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
|
||||
);
|
||||
```
|
||||
|
||||
`input` プロパティを空文字列に設定して `ControlledInput` の state を初期化します。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(ControlledInput)).state('input'),
|
||||
''
|
||||
);
|
||||
```
|
||||
|
||||
input 要素への入力で、state と input の value を更新し、`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>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
これで、`React.Component` クラスを拡張した ES6 クラス `Kitten` が作成され、 `Kitten` クラスから、ローカルの state やライフサイクルフックといった数多くの便利な React 機能にアクセスできるようになりました。 まだこれらの用語に慣れていなくても心配はいりません。以降のチャレンジで詳しく説明します。 また、`Kitten` クラスの中に、`super()` を呼び出す `constructor` が定義されていることに注目してください。 このコンストラクターは、`super()` を使用して親クラス (この例では `React.Component`) のコンストラクターを呼び出します。 コンストラクターは、`class` キーワードで作成されたオブジェクトの初期化時に使用される特別なメソッドです。 `super` を使用してコンポーネントの `constructor` を呼び出し、両方に `props` を渡すことをお勧めします。 これにより、コンポーネントが正しく初期化されます。 ここでは、このコードを含めることが標準的であることを知っておいてください。 このあと、コンストラクターや `props` の他の用法について説明していきます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターで、`MyComponent` が class 構文を使用して定義されています。 `Hello React!` というテキストを持つ `h1` を含む `div` 要素を返すように、`render` メソッドの記述を完成させてください。
|
||||
|
||||
# --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 によって作成され維持されているオープンソースビューライブラリで、 最新のウェブアプリケーションのユーザーインターフェイス (UI) をレンダーするのに適したツールです。
|
||||
|
||||
React では、JavaScript の中に直接 HTML を記述できるようにする、JSX と呼ばれる JavaScript の構文拡張を使用しています。 これにはいくつかの利点があります。 HTML の中で JavaScript のプログラミング機能を最大限発揮することができ、またコードが読みやすくなります。 ほとんどの場合、JSX はすでにご存知の HTML に似ていますが、いくつかの重要な違いがあります。それらの違いについてこのチャレンジ全体を通して説明します。
|
||||
|
||||
たとえば、JSX は JavaScript の構文の拡張であるため、実際に JSX の中に JavaScript を直接記述することができます。 それには次のように、JavaScript として扱いたいコードを中括弧で囲むだけです: `{ 'this is treated as JavaScript code' }`。 この記法は以降のチャレンジでも使用していますので、覚えておいてください。
|
||||
|
||||
ただし、JSX は有効な JavaScript ではないため、JSX コードを JavaScript にコンパイルする必要があります。 この処理によく使用されているツールが、Babel というトランスパイラーです。 このツールは、このチャレンジで利用できるようにすでに追加されています。 構文的に無効な JSX を記述した場合は、このチャレンジの最初のテストが失敗することがわかります。
|
||||
|
||||
ちなみに、チャレンジでは見えないところで `ReactDOM.render(JSX, document.getElementById('root'))` を呼び出しています。 この関数呼び出しによって、JSX が React 独自の軽量の DOM 表現に置かれます。 そして、React は独自の DOM のスナップショットを使用して最適化を行い、実際の 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 (状態、ステート) は、アプリケーションが認識する必要のあるデータで構成され、時間とともに変化する可能性があります。 アプリでは通常、必要に応じて状態の変化に応答し、更新された UI を表示します。 React は、最新のウェブアプリケーションの状態管理に適したソリューションを備えています。
|
||||
|
||||
React コンポーネントで state を作成するには、`constructor` のコンポーネントクラスで `state` プロパティを宣言します。 これにより、コンポーネントが作成時に `state` で初期化されます。 `state` プロパティは JavaScript の `object` に設定する必要があります。 宣言は次のようになります。
|
||||
|
||||
```jsx
|
||||
this.state = {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
コンポーネントが存在している間は、`state` オブジェクトにアクセスすることができます。 更新したり、UI にレンダーしたり、props として子コンポーネントに渡したりすることができます。 `state` オブジェクトは必要に応じて単純なものにも複雑なものにもすることができます。 こうした `state` を作成するには、`React.Component` を拡張してクラスコンポーネントを作成する必要があります。
|
||||
|
||||
# --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
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`StatefulComponent` の state を初期化し、プロパティ `name` に文字列を設定します。
|
||||
|
||||
```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 コンポーネントは 2 つの方法で作成できます。 1 つ目は JavaScript 関数を使用する方法です。 この方法でコンポーネントを定義すると、*ステートレス関数型コンポーネント*が作成されます。 アプリケーションにおける state (状態、ステート) の概念については以降のチャレンジで説明しますが、 ここではステートレスコンポーネントを「データを受け取ってレンダーすることができるが、データの変更を管理または追跡することをしないコンポーネント」であると理解してください (React コンポーネントを作成する 2 つ目の方法については次のチャレンジで説明します)。
|
||||
|
||||
関数を持つコンポーネントを作成するには、JSX または `null` を返す JavaScript 関数を記述するだけです。 ここで重要なのは、React では関数名を大文字で始める必要がある、ということです。 JSX で HTML クラスを割り当てるステートレス関数型コンポーネントの例を次に示します。
|
||||
|
||||
```jsx
|
||||
const DemoComponent = function() {
|
||||
return (
|
||||
<div className='customClass' />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
トランスパイル後に、`<div>` は `customClass` という CSS クラスを持ちます。
|
||||
|
||||
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 クラスを定義する
|
||||
challengeType: 6
|
||||
forumTopicId: 301393
|
||||
dashedName: define-an-html-class-in-jsx
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
JSX の記述に慣れてくると、HTML とどう違うのかと不思議に思うかもしれません。
|
||||
|
||||
ここまで、HTML と JSX はまったく同じもののように思えるかもしれません。
|
||||
|
||||
JSX の重要な違いの一つは、単語 `class` を使用して HTML クラスを定義することができなくなったことです。 これは、`class` が JavaScript で予約語となっているからです。 代わりに、JSX では `className` を使用します。
|
||||
|
||||
実際、JSX での HTML 属性とイベント参照の命名規則はすべて、キャメルケースになります。 たとえば、JSX の click イベントは `onClick` であり、`onclick` ではありません。 同様に、`onchange` は `onChange` になります。 微妙な違いですが、今後重要になりますので覚えておいてください。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`myDiv` というクラスを、JSX コードで提供された `div` に適用してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
定数 `JSX` から `div` 要素を返します。
|
||||
|
||||
```js
|
||||
assert.strictEqual(JSX.type, 'div');
|
||||
```
|
||||
|
||||
`div` に `myDiv` というクラスを持たせます。
|
||||
|
||||
```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()` では、前回のチャレンジと同様に、配列を順序なしリストにマッピングする必要があります。 `frontEndFrameworks` 配列内の各フレームワークの `li` 要素を返すように、`map` コールバックの記述を完成させてください。 今回は、それぞれの `li` に `key` 属性を付けて、一意の値を設定してください。 また、`li` 要素には `frontEndFrameworks` のテキストも含めてください。
|
||||
|
||||
通常は、レンダーされる要素を一意に識別するキーのようなものを作成します。 配列インデックスを使用する方法もありますが、通常は一意の識別子を使用するようにしてください。
|
||||
|
||||
# --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,103 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036181
|
||||
title: インラインスタイルを導入する
|
||||
challengeType: 6
|
||||
forumTopicId: 301395
|
||||
dashedName: introducing-inline-styles
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
他にも、React のコードに強力な機能を追加する複雑な概念があります。 しかし、もっと単純な問題として、React で作成した JSX 要素のスタイルをどのように設定するのでしょうか? [JSX 要素にクラスを適用する方法](/learn/front-end-development-libraries/react/define-an-html-class-in-jsx)があるので、HTML の場合とまったく同じではないことは理解していると思います。
|
||||
|
||||
スタイルシートからスタイルをインポートする場合は、それほど違いはありません。 `className` 属性を使用して JSX 要素にクラスを適用し、スタイルシートでクラスにスタイルを適用します。 もう一つの方法として、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 はスタイルオブジェクトでケバブケースのキーを受け付けないためです。 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--
|
||||
|
||||
ここまで、HTML クラスの定義方法について、HTML では `class` を使用するのに対し、JSX では `className` を使用するという違いがあることを説明しました。
|
||||
|
||||
もう一つ、JSX が HTML と異なる重要な点として挙げられるのが、自己終了タグの概念です。
|
||||
|
||||
HTML では、ほとんどすべてのタグに開始タグと終了タグの両方があり (`<div></div>` など)、終了タグ名の前には常にスラッシュが付いています。 ただし、HTMLには「自己終了タグ」という特別な記法があります。これは、別のタグを開始する前に開始と終了の両方を必要としないタグです。
|
||||
|
||||
たとえば、改行タグは `<br>` または `<br />` と記述できますが、`<br></br>` と記述することはありません。これは内容が含まれていないためです。
|
||||
|
||||
JSX では、こうした規則は少し異なります。 どの 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` を比較することで、この動作を回避することができます。 メソッドからは、コンポーネントの更新が必要かどうかを React に伝える `boolean` 値を返す必要があります。 現在の props (`this.props`) を次の props (`nextProps`) と比較して、更新する必要があるかどうかを判断し、それに応じて `true` または `false` を返すことができます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`shouldComponentUpdate()` メソッドが `OnlyEvens` というコンポーネントに追加されています。 現在、このメソッドは `true` を返すため、`OnlyEvens` は新しい `props` を受け取るたびに再レンダーされます。 メソッドを変更して、新しい props の `value` が偶数の場合にのみ `OnlyEvens` を更新するようにしてください。 `Add` ボタンをクリックして、ライフサイクルフックがトリガーされるときにブラウザーのコンソールでイベントの順序を確認してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
`Controller` コンポーネントで、`OnlyEvens` コンポーネントを子としてレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Controller));
|
||||
return (
|
||||
mockedComponent.find('Controller').length === 1 &&
|
||||
mockedComponent.find('OnlyEvens').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`shouldComponentUpdate` メソッドを `OnlyEvens` コンポーネントで定義します。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(() => {
|
||||
const child = React.createElement(OnlyEvens)
|
||||
.type.prototype.shouldComponentUpdate.toString()
|
||||
.replace(/s/g, '');
|
||||
return child !== 'undefined';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`OnlyEvens` コンポーネントから、`this.props.value` の値をレンダーする `h1` タグを返します。
|
||||
|
||||
```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 を設定することができます。 デフォルトの props を上書きするには、コンポーネントの props の値を明示的に設定します。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`ShoppingCart` コンポーネントで現在、子コンポーネント `Items` をレンダーしています。 この `Items` コンポーネントにはデフォルトの props `quantity` があり、整数 `0` に設定されています。 `quantity` に値 `10` を渡してデフォルトの prop を上書きしてください。
|
||||
|
||||
**注:** コンポーネントに prop を追加するための構文は HTML 属性を追加する方法に似ていますが、 `quantity` の値は整数であるため、引用符で囲まず、中括弧で囲む必要があります。 たとえば `{100}` のように記述します。 この構文によって、括弧内の値を直接 JavaScript として解釈するよう JSX に指示します。
|
||||
|
||||
# --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` コンポーネントに、 `ShoppingCart` コンポーネントから渡された `{ quantity: 10 }` という prop を持たせます。
|
||||
|
||||
```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--
|
||||
|
||||
コードエディターに 3 つのコンポーネントの概略が記されています。 `MyApp` コンポーネントは、`GetInput` および `RenderInput` という子コンポーネントをレンダーする親コンポーネントです。 `MyApp` の render メソッドに `GetInput` コンポーネントを追加し、`MyApp` の `state` から `inputValue` に割り当てられた `input` という prop を渡してください。 また、`handleChange` という prop を作成して、それに入力ハンドラー `handleChange` を渡してください。
|
||||
|
||||
次に、`MyApp` の render メソッドに `RenderInput` を追加し、`input` という prop を作成して、`state` からの `inputValue` を渡してください。 記述を終えると、`GetInput` コンポーネントの `input` フィールドに入力できるようになり、その親のハンドラーメソッドが props 経由で呼び出されます。 これにより、親の `state` の入力が更新され、両方の子要素に 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` をレンダーするときに、to-do タスクの配列 (たとえば `["walk dog", "workout"]` など) が割り当てられた `tasks` プロパティを渡してください。 次に、`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` コンポーネントの 3 つ目の子要素を、`List` コンポーネントのインスタンスにします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return mockedComponent.children().first().childAt(2).name() === 'List';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`ToDo` コンポーネントの 5 つ目の子要素を、`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)
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
今日のタスクを表す 1 つ目の `List` コンポーネントに、2 つ以上のアイテムを持たせます。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return mockedComponent.find('List').get(0).props.tasks.length >= 2;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
明日のタスクを表す 2 つ目の `List` コンポーネントに、3 つ以上のアイテムを持たせます。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ToDo));
|
||||
return mockedComponent.find('List').get(1).props.tasks.length >= 3;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`List` コンポーネントで、`tasks` prop からの値を `p` タグにレンダーします。
|
||||
|
||||
```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 では props、つまりプロパティを子コンポーネントに渡すことができます。 たとえば `App` コンポーネントがあり、そこでステートレス関数型コンポーネントである `Welcome` という子コンポーネントをレンダーするとします。 次のように記述することで、`Welcome` に `user` プロパティを渡すことができます。
|
||||
|
||||
```jsx
|
||||
<App>
|
||||
<Welcome user='Mark' />
|
||||
</App>
|
||||
```
|
||||
|
||||
作成したのは**カスタムの HTML 属性**であり、React によってサポートされ、コンポーネントに渡すことができます。 この例では、作成したプロパティ `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` コンポーネントの 2 つ目の子を `CurrentDate` コンポーネントにします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Calendar));
|
||||
return mockedComponent.children().childAt(1).name() === 'CurrentDate';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`CurrentDate` コンポーネントに `date` という prop を持たせます。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(Calendar));
|
||||
return mockedComponent.children().childAt(1).props().date;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`CurrentDate` の `date` prop に、テキストの文字列を含めます。
|
||||
|
||||
```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` prop を生成します。
|
||||
|
||||
```js
|
||||
assert(/<CurrentDatedate={Date\(\)}\/>/.test(__helpers.removeWhiteSpace(code)));
|
||||
```
|
||||
|
||||
`CurrentDate` コンポーネントで、`date` prop からの値を `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--
|
||||
|
||||
ここまでのチャレンジで、子の JSX 要素や子の React コンポーネントに props を渡す例をたくさん紹介してきました。 これらの props はいったいどこからやって来るのでしょうか。 よくあるパターンとしては、アプリにとって重要な `state` を含むステートフルコンポーネントを用意し、それから子コンポーネントをレンダーします。 そしてそれらのコンポーネントから、props として渡された `state` の一部にアクセスできるようにします。
|
||||
|
||||
たとえば `App` コンポーネントがあり、他のコンポーネントの中から `Navbar` をレンダーするとします。 `App` には `state` があり、そこにはたくさんのユーザー情報が含まれていますが、`Navbar` ではユーザーのユーザー名にアクセスして表示できれば十分です。 そこで、`state` のうちその部分を prop として `Navbar` コンポーネントに渡します。
|
||||
|
||||
こうしたパターンは React での重要な決まりごとをいくつか示しています。 1 つ目は*一方向のデータフロー*です。 state は、ステートフルな親コンポーネントから子コンポーネントに向かって、アプリケーションのコンポーネントツリーを一方向に流れます。 子コンポーネントでは必要な state データのみを受け取ります。 2 つ目は、複雑なステートフルアプリを、ほんの数個の、またはおそらく単一の、ステートフルコンポーネントに分割できることです。 残りのコンポーネントでは、単純に親コンポーネントから state を props として受け取り、その state から UI をレンダーします。 その結果、コードのある部分で state の管理を処理し、別の部分で UI のレンダーを処理するという、コードの分離ができ始めます。 state のロジックを UI のロジックから切り分けるというこの原則は、React の主要な原則の一つとなっています。 これを正しく適用すれば、複雑なステートフルアプリケーションの設計管理がずっと簡単になります。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`MyApp` はステートフルコンポーネントで、`Navbar` コンポーネントを子としてレンダーします。 `state` の `name` プロパティを子コンポーネントに渡し、`h1` タグに `name` を表示してください。これは `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` 要素で、`name` prop をレンダーします。
|
||||
|
||||
```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: クラスコンポーネントを 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)`。 1 つ目の引数は、レンダーする React コンポーネントです。 2 つ目の引数は DOM ノードで、この中にコンポーネントをレンダーします。
|
||||
|
||||
React コンポーネントは、JSX 要素とは少し異なる形で `ReactDOM.render()` に渡されます。 JSX 要素の場合は、レンダーする要素の名前を渡します。 しかし React コンポーネントの場合は、ネストされたコンポーネントをレンダーする場合と同じ構文を使用する必要があります (例: `ReactDOM.render(<ComponentToRender />, targetNode)`)。 この構文は ES6 クラスコンポーネントの場合でも関数型コンポーネントの場合でも使用します。
|
||||
|
||||
# --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` コンポーネントを DOM にレンダーします。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`、`&&`、および三項演算子 (` 条件 ? True の場合の式 : False の場合の式`) を使用する方法を紹介しました。 しかし、まだ説明していない重要なトピックが一つあります。それは、これらの概念はそのいずれかまたはすべてを、React の別の強力な機能である props と組み合わせることができる、ということです。 props を使用してコードを条件付きでレンダーすることは、React の開発者にとってはごく一般的な作業です。つまり、与えられた prop の値を利用することで、何を表示するかを自動的に決めることができます。
|
||||
|
||||
このチャレンジでは、props に基づいてレンダー処理を決定する子コンポーネントを設定します。 また、三項演算子も使用しますが、前のチャレンジで説明した他のいくつかの概念が、このチャレンジでも同じように役立つかもしれません。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに 2 つのコンポーネントがあって、一部が定義されています。1 つは `GameOfChance` という親で、もう 1 つは `Results` という子です。 これらを使用して、ユーザーがボタンを押して勝ったか負けたかを表示する簡単なゲームを作成します。
|
||||
|
||||
まず、実行のたびに異なる値をランダムに返す簡単な式が必要です。 これには `Math.random()` を使用できます。 このメソッドは、呼び出されるたびに `0` (含む) ~ `1` (含まない) の間の値を返します。 そのため、確率を 50/50 にする場合は `Math.random() >= .5` という式を使用します。 統計的に言えば、この式は 50% の確率で `true` を返し、残りの 50% の確率で `false` を返します。 render メソッドで、`null` の部分を前述の式に置き換えて変数宣言を完成させてください。
|
||||
|
||||
これで、コードでランダムな決定を行うのに使用できる式ができました。 次に、この式を実装する必要があります。 `Results` コンポーネントを `GameOfChance` の子としてレンダーし、`expression` を `fiftyFifty` という prop として渡してください。 `Results` コンポーネントで、`GameOfChance` から渡される `fiftyFifty` prop に基づいて、`You Win!` または `You Lose!` というテキストを使用して `h1` 要素をレンダーする三項式を記述してください。 最後に、`handleClick()` メソッドで、各ターンを正しくカウントし、ユーザーが自分のプレイした回数を確認できるようにしてください。 こうすることで、2 連勝または 2 連敗した場合にコンポーネントが実際に更新されたことをユーザーに伝えることもできます。
|
||||
|
||||
# --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 を初期化し、`counter` のプロパティを値 `1` に設定します。
|
||||
|
||||
```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'
|
||||
);
|
||||
```
|
||||
|
||||
ボタンがクリックされるたびに、カウンターの state の値を 1 ずつ増やし、テキスト `Turn: N` が含まれている DOM に単一の `p` 要素をレンダーします。ここで `N` はカウンターの state の値です。
|
||||
|
||||
```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 にマウントされたら、その後ボタンがクリックされるたびに、`You Win!` または `You Lose!` のいずれかをランダムにレンダーする単一の `h1` 要素を返します。
|
||||
|
||||
```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 では、ReactDOM と呼ばれる React のレンダー API を使用して、この JSX を直接、HTML の DOM にレンダーすることができます。
|
||||
|
||||
ReactDOM では、DOM に React の要素をレンダーする次のような簡単なメソッドが用意されています: `ReactDOM.render(componentToRender, targetNode)`。ここで、1 つ目の引数はレンダーする React の要素またはコンポーネントで、2 つ目の引数はコンポーネントのレンダー先となる DOM ノードです。
|
||||
|
||||
予想されるとおり、`ReactDOM.render()` は JSX 要素宣言の後に呼び出す必要があります。変数を使用前に宣言する必要があるのと同様です。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに単純な JSX コンポーネントがあります。 `ReactDOM.render()` メソッドを使用して、このコンポーネントをページにレンダーしてください。 定義済みの JSX 要素を 1 つ目の引数として直接渡し、それらのレンダー先となる DOM ノードを `document.getElementById()` を使用して選択できます。 `id='challenge-node'` を持つ `div` を使用できます。 `JSX` 定数は変更しないでください。
|
||||
|
||||
# --hints--
|
||||
|
||||
定数 `JSX` から `div` 要素を返します。
|
||||
|
||||
```js
|
||||
assert(JSX.type === 'div');
|
||||
```
|
||||
|
||||
`div` に 1 つ目の要素として `h1` タグを含めます。
|
||||
|
||||
```js
|
||||
assert(JSX.props.children[0].type === 'h1');
|
||||
```
|
||||
|
||||
`div` に 2 つ目の要素として `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()` メソッドが用意されていて、この目的に使用できます。
|
||||
|
||||
実世界のアプリでは、主に 2 つの理由でサーバーでのレンダーを使用する場合があります。 1 つ目として、これを行わない場合の React アプリは、初めてブラウザーに読み込まれるときに、空に近い HTML ファイルと JavaScript の大きなバンドルで構成されることになりますが、 こうした構成は、ページのコンテンツのインデックスを生成しようとしている検索エンジンにとって理想的ではなく、ページが人々に検索されにくくなるかもしれません。 サーバーで初めて HTML マークアップをレンダーしてクライアントに送信する場合、初回のページ読み込みには、検索エンジンでクロールできるページのマークアップがすべて含まれます。 2 つ目として、レンダーされる HTML はアプリ全体の JavaScript コードよりも小さく、そのため初回のページ読み込みが高速になります。 それでも React はアプリを認識し、初回の読み込み後もアプリを管理することができます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`ReactDOMServer` 上に `renderToString()` メソッドが用意されていて、ここでグローバルオブジェクトとして利用できます。 このメソッドは React 要素である引数を 1 つ受け取ります。 これを使用して `App` を文字列にレンダーしてください。
|
||||
|
||||
# --hints--
|
||||
|
||||
`ReactDOMServer.renderToString` を使用して `App` コンポーネントを文字列にレンダーします。
|
||||
|
||||
```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: 別の方法でユーザーインターフェイスに state をレンダーする
|
||||
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 ステートメントで変数 `name` を使用して、この値を `h1` タグにレンダーしてください。 return ステートメントでは JSX の構文を使用する必要があります (JavaScript の場合は中括弧を付けます) 。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` で、キー `name` を設定し、その state に値 `freeCodeCamp` を格納します。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('name') ===
|
||||
'freeCodeCamp'
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` で、`h1` 見出し要素を単一の `div` で囲んでレンダーします。
|
||||
|
||||
```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: ユーザーインターフェイスに state をレンダーする
|
||||
challengeType: 6
|
||||
forumTopicId: 301409
|
||||
dashedName: render-state-in-the-user-interface
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
コンポーネントの初期状態を定義したら、レンダーされる UI に、そのコンポーネントの任意の部分を表示することができます。 コンポーネントがステートフルの場合は、コンポーネントから常に自身の `render()` メソッドの中で `state` のデータにアクセスできます。 データへのアクセスには `this.state` を使用できます。
|
||||
|
||||
render メソッドの `return` の中で state 値にアクセスしたい場合は、値を中括弧で囲む必要があります。
|
||||
|
||||
`state` は React で最も強力なコンポーネントの機能の一つです。 アプリの重要なデータを追跡して、このデータの変更に応じて UI をレンダーすることができます。 データが変更されると、UI が変更されます。 React では、仮想 DOM と呼ばれるものを使用して、見えないところで変更を追跡しています。 state のデータが更新されると、データを prop として受け取った子コンポーネントを含めて、そのデータを使用しているコンポーネントの再レンダーをトリガーします。 実際の DOM も更新しますが、必要な場合に限られます。 つまり、DOM の変更を気にする必要はない、ということです。 単に UI をどのように表示させるかを宣言するだけです。
|
||||
|
||||
あるコンポーネントをステートフルにした場合、他のコンポーネントはその `state` を認識しないことに注意してください。 state データを `props` として子コンポーネントに渡さない限り、`state` は完全にカプセル化されるか、またはそのコンポーネントに対してローカルになります。 `state` のカプセル化という考え方はとても重要です。なぜなら、特定のロジックを記述して、そのロジックをコード内の 1 か所に閉じ込めて分離しておけるからです。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターにある `MyComponent` はすでにステートフルになっています。 コンポーネントの render メソッドで `h1` タグを定義し、コンポーネントの state から `name` の値をレンダーしてください。
|
||||
|
||||
**注:** `h1` では `state` の値のみをレンダーし、それ以外はレンダーしないでください。 JSX では、中括弧 `{ }` で記述されたコードはすべて JavaScript として扱われます。 そのため、`state` から値にアクセスするには参照を中括弧で囲みます。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` で、キー `name` を設定し、その state に値 `freeCodeCamp` を格納します。
|
||||
|
||||
```js
|
||||
assert(
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('name') ===
|
||||
'freeCodeCamp'
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` で、`h1` 見出し要素を単一の `div` で囲んでレンダーします。
|
||||
|
||||
```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 を使用して制御する別の用例として、レンダーされる要素を条件に結び付けることができます。 条件が true の場合は、あるビューをレンダーし、 条件が false の場合は別のビューをレンダーします。 これを行うには、React コンポーネントの `render()` メソッドで標準の `if/else` ステートメントを使用します。
|
||||
|
||||
# --instructions--
|
||||
|
||||
MyComponent の state に、UI に要素を表示するかどうかを追跡する `boolean` が含まれています。 `button` は、この値の state を切り替えます。 現時点では、毎回同じ UI をレンダーします。 `render()` メソッドを `if/else` ステートメントで書き換えて、`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 メソッドでは、`this.state.display` の条件をチェックするために `if/else` ステートメントを使用する必要があります。
|
||||
|
||||
```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 が渡されるたびに同じビューを返します。 state (状態、ステート) とは何なのかと思うかもしれません。それについては次のチャレンジで詳しく説明します。 その前に、ここではコンポーネントの用語について復習をします。
|
||||
|
||||
*ステートレス関数型コンポーネント*とは、props を受け取り、JSX を返すように記述する関数です。 一方、*ステートレスコンポーネント*とは `React.Component` を拡張するクラスですが、内部状態を使用しません (次のチャレンジで説明します)。 そして、*ステートフルコンポーネント*とは、自身の内部状態を維持するクラスコンポーネントです。 ステートフルコンポーネントのことを単に「コンポーネント」または「React コンポーネント」と呼ぶこともあります。
|
||||
|
||||
ステートフルを最小限に抑え、できるだけステートレスな関数型コンポーネントを作成するのが、通常のパターンです。 そうすることで、状態管理がアプリケーションの特定の領域に閉じ込められるようになります。 したがって、状態の変更がアプリの動作にどのように影響するかを簡単に追跡できるようになり、アプリの開発や保守が向上します。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに `CampSite` コンポーネントがあります。このコンポーネントは、`Camper` コンポーネントを子としてレンダーします。 `Camper` コンポーネントを定義し、それに `{ name: 'CamperBot' }` というデフォルトの props を割り当ててください。 `Camper` コンポーネントの中で必要なコードをレンダーしてください。ただし、`prop` として渡された `name` の値のみを含む `p` 要素を 1 つ、必ず設定してください。 最後に、`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 を含めます。この props は、文字列 `CamperBot` をキー `name` に割り当てます。
|
||||
|
||||
```js
|
||||
assert(
|
||||
/Camper.defaultProps={name:(['"`])CamperBot\1,?}/.test(
|
||||
__helpers.removeWhiteSpace(code)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`Camper` コンポーネントに prop types を含めます。prop types では、`name` prop を `string` 型にする必要があります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
/Camper.propTypes={name:PropTypes.string.isRequired,?}/.test(
|
||||
__helpers.removeWhiteSpace(code)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`Camper` コンポーネントに `p` 要素を含め、`name` prop のテキストのみを含めます。
|
||||
|
||||
```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 を使用して state を設定する
|
||||
challengeType: 6
|
||||
forumTopicId: 301412
|
||||
dashedName: set-state-with-this-setstate
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
以前のチャレンジで、コンポーネントの `state` と、`constructor` での state の初期化方法について説明しました。 コンポーネントの `state` を変更する方法もあります。 React には、コンポーネントの `state` を更新するための `setState` というメソッドが用意されています。 `setState` メソッドはコンポーネントクラスの中で `this.setState()` のように呼び出し、キーと値のペアを持つオブジェクトを渡します。 キーは state のプロパティであり、値は更新された state データです。 たとえば、`username` を state に保存していて、それを更新したい場合は、次のようにします。
|
||||
|
||||
```jsx
|
||||
this.setState({
|
||||
username: 'Lewis'
|
||||
});
|
||||
```
|
||||
|
||||
React では `state` を直接変更しないことが前提となっています。代わりに、state が変更された場合は常に `this.setState()` を使用します。 また、React ではパフォーマンスを向上させるために複数の state の更新がバッチ処理されることがあります。 このため、`setState` メソッドによる state の更新が非同期になる可能性があります。 この問題を回避する方法として、`setState` メソッドの代わりとなる構文があります。 必要になることはめったにありませんが、覚えておくと役に立ちます。 詳細は [React のドキュメント](https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous)を参照してください。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに `button` 要素があり、`onClick()` ハンドラーが設定されています。 このハンドラーは、`button` がブラウザーで click イベントを受信したときにトリガーされ、`MyComponent` で定義されている `handleClick` メソッドを実行します。 `handleClick` メソッドの中で、`this.setState()` を使用してコンポーネントの `state` を更新してください。 `state` 内の `name` プロパティに文字列 `React Rocks!` を設定してください。
|
||||
|
||||
ボタンをクリックして、レンダーされる state が更新されることを確認してください。 ここでは click ハンドラーのコードの動作を十分に理解していなくても心配はいりません。 それについては以降のチャレンジで説明します。
|
||||
|
||||
# --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: 条件をもっと簡潔にするために && を使用する
|
||||
challengeType: 6
|
||||
forumTopicId: 301413
|
||||
dashedName: use--for-a-more-concise-conditional
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
前回のチャレンジでは `if/else` ステートメントが正しく機能しましたが、もっと簡潔な方法で同じ結果を得ることができます。 コンポーネントで複数の条件を追跡していて、それぞれの条件に応じて異なる要素をレンダーしたいとしましょう。 わずかに異なる UI を返す `else if` ステートメントをたくさん記述すると、エラーの余地を残すコードを繰り返してしまう可能性があります。 代わりに、`&&` 論理演算子を使用して、もっと簡潔な方法で条件ロジックを実行することができます。 ここでは、条件が `true` かどうかをチェックして、true ならばマークアップを返すという処理なので、この演算子を使用できます。 例:
|
||||
|
||||
```jsx
|
||||
{condition && <p>markup</p>}
|
||||
```
|
||||
|
||||
`condition` が `true` の場合は、マークアップを返します。 condition が `false` の場合は、`condition` の評価後にすぐに `false` が返されるため、何も返しません。 これらのステートメントを JSX や文字列の複数の条件に直接含め、それぞれのステートメントの後に `&&` を記述してまとめることができます。 これにより、たくさんのコードを繰り返さなくても、`render()` メソッドでもっと複雑な条件ロジックを処理することができます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
もう一度前の例を解決してください。`display` が `true` の場合にのみ `h1` をレンダーしますが、`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 の中で条件付きロジックを実装したい場合に、三項式は代わりの手段としてとても便利です。 三項演算子には 3 つのパートがありますが、複数の三項式を組み合わせることもできます。 基本的な構文は次のとおりです。
|
||||
|
||||
```jsx
|
||||
condition ? expressionIfTrue : expressionIfFalse;
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに 3 つの定数があり、`CheckUserAge` コンポーネントの `render()` メソッドの中で定義されています。 これらは `buttonOne`、`buttonTwo`、`buttonThree` という名前で、 ボタン要素を表す単純な JSX 式がそれぞれに割り当てられています。 まず、`CheckUserAge` の state を `input` と `userAge` で初期化し、両方に空の文字列の値を設定してください。
|
||||
|
||||
コンポーネントから情報がページにレンダーされると、ユーザーがそれらの情報とやり取りする手段が必要になります。 コンポーネントの `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'
|
||||
);
|
||||
```
|
||||
|
||||
`input` 要素に 18 よりも小さい数字が入力されて `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'
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
`input` 要素に 18 以上の数字が入力されて `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 へのアクセス、コードへのコメントの挿入、そして前回はコンポーネントのスタイル設定などの作業で使用しました。 これらはすべて JSX で JavaScript を使用するための一般的な用例ですが、これ以外の方法でも React コンポーネントで JavaScript コードを利用することができます。
|
||||
|
||||
中括弧の内側に***挿入せずに***、`return` ステートメントの前で、`render` メソッドで JavaScript を直接記述することもできます。 なぜなら、まだ JSX コードの中にないからです。 後で JSX コードの `return` ステートメントの*内側*で変数を使用したい場合は、変数名を中括弧の中に置きます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
ここに示したコードの `render` メソッドにある配列には、1980 年代のマジックエイトボールという玩具で映し出される答えを表す 20 個のフレーズが格納されています。 ボタンの click イベントが `ask` メソッドにバインドされているため、ボタンがクリックされるたびに乱数が生成され、`randomIndex` として state に格納されます。 52 行目の文字列 `change me!` を削除し、`answer` 定数を割り当て直して、コンポーネントが更新されるたびにコードから `possibleAnswers` 配列の別のインデックスにランダムにアクセスするようにしてください。 最後に、`answer` 定数を `p` タグの中に挿入してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MagicEightBall` コンポーネントが存在し、ページにレンダーする必要があります。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MagicEightBall)).find('MagicEightBall')
|
||||
.length,
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
`MagicEightBall` の 1 つ目の子を `input` 要素にします。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MagicEightBall))
|
||||
.children()
|
||||
.childAt(0)
|
||||
.name(),
|
||||
'input'
|
||||
);
|
||||
```
|
||||
|
||||
`MagicEightBall` の 3 つ目の子を `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` コンポーネントは、`possibleAnswers` 配列からのランダムな要素を含む `p` 要素を返します。
|
||||
|
||||
```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` があります。このメソッドは、条件に基づいて配列の内容を絞り込み、新しい配列を返します。 たとえばユーザーの配列があり、すべてのユーザーがプロパティ `online` を持っていて、このプロパティを `true` または `false` に設定できる場合、次のように記述してオンラインのユーザーのみに絞り込むことができます。
|
||||
|
||||
```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` を返し、さらに、オンラインステータスが `true` に設定されているすべてのユーザーの `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 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--
|
||||
|
||||
条件付きレンダーは便利ですが、コンポーネントでレンダーする必要のある要素の数がわからない場合もあります。 リアクティブなプログラミングでは多くの場合、アプリケーションの状態は実行時までわかりません。なぜなら、ユーザーとそのプログラムのやり取りに大きく依存しているからです。 そうした未知の状態を事前に正しく処理するコードを記述する必要があります。 こうした概念に対応できるのが、React での `Array.map()` の使用です。
|
||||
|
||||
たとえば、シンプルな「To Do List」アプリを作成するとします。 ユーザーが各自のリストにいくつのアイテムを持っているかは、プログラマーにはわかりません。 プログラムを使用している誰かが「今日は洗濯の日だ」と決めるよりもずっと前に、リスト要素の正しい数を動的にレンダーするようにコンポーネントを設定する必要があります。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターで、`MyToDoList` コンポーネントのほとんどが設定済みです。 制御されたフォームのチャレンジを完了しているのであれば、このコードの一部に見覚えがあると思います。 `textarea` と `button` があり、ユーザーの state を追跡するいくつかのメソッドがありますが、まだ何もページにレンダーされません。
|
||||
|
||||
`constructor` の中で、`this.state` オブジェクトを作成し、2 つの状態を定義してください。1 つは `userInput` で、空の文字列として初期化してください。もう 1 つは `toDoList` で、空の配列として初期化してください。 次に、`items` 変数の隣の `render()` メソッドにあるコメントを削除してください。 その場所で、コンポーネントの内部状態に格納されている `toDoList` 配列全体をマップし、各アイテムの `li` を動的にレンダーしてください。 文字列 `eat, code, sleep, repeat` を `textarea` に入力してボタンをクリックし、何が起きるかを確かめてみてください。
|
||||
|
||||
**注:** ご存知かもしれませんが、このようなマッピング操作によって作成される兄弟の子要素にはすべて、一意の `key` 属性を付ける必要があります。 心配はいりません。次のチャレンジでこのトピックを取り上げます。
|
||||
|
||||
# --hints--
|
||||
|
||||
MyToDoList コンポーネントが存在し、ページにレンダーする必要があります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
return mockedComponent.find('MyToDoList').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyToDoList` の 1 つ目の子を `textarea` 要素にします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
return (
|
||||
mockedComponent.find('MyToDoList').children().childAt(0).type() ===
|
||||
'textarea'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyToDoList` の 2 つ目の子を `br` 要素にします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyToDoList));
|
||||
return (
|
||||
mockedComponent.find('MyToDoList').children().childAt(1).type() === 'br'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`MyToDoList` の 3 つ目の子を `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 によってデフォルトの props が割り当てられます。 このため、prop の値が明示的に指定されていない場合に、その値を指定することができます。 たとえば、`MyComponent.defaultProps = { location: 'San Francisco' }` と宣言した場合は、特に指定しない限り、location という prop を定義して文字列 `San Francisco` を設定したことになります。 props が未定義の場合は、React によってデフォルトの props が割り当てられます。ただし、prop の値として `null` を渡した場合は `null` のままになります。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに `ShoppingCart` コンポーネントが表示されています。 このコンポーネントで、`0` の値を持つ prop `items` を指定するデフォルトの props を定義してください。
|
||||
|
||||
# --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` を設定することをお勧めします。 `defaultProps` の定義と同じ方法で、コンポーネントの `propTypes` プロパティを定義できます。 こうすることで、与えられたキーの props が与えられた型であるかどうかがチェックされます。 `handleClick` という prop に `function` 型を要求する例を次に示します。
|
||||
|
||||
```js
|
||||
MyComponent.propTypes = { handleClick: PropTypes.func.isRequired }
|
||||
```
|
||||
|
||||
この例では、`PropTypes.func` の部分で `handleClick` が関数かどうかをチェックします。 `isRequired` を追加することで、`handleClick` がそのコンポーネントに必要なプロパティであることを React に伝えます。 その prop が指定されていない場合は警告が表示されます。 また、`func` は `function` を表します。 JavaScript の 7 つのプリミティブ型の中で、`function` と `boolean` (`bool` と記述) の 2 つだけは通常と異なるスペルを使用します。 プリミティブ型に加えて、他にも利用可能な型があります。 たとえば、prop が React の要素かどうかをチェックできます。 オプションの一覧については、[ドキュメント](https://reactjs.org/docs/typechecking-with-proptypes.html#proptypes)を参照してください。
|
||||
|
||||
**注:** React v15.5.0 では `PropTypes` は React とは独立してインポートされます (例: `import PropTypes from 'prop-types';`)。
|
||||
|
||||
# --instructions--
|
||||
|
||||
prop として `quantity` を要求する `propTypes` を `Items` コンポーネントで定義し、`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` の値を要求し、その値が数値であることを確認します。
|
||||
|
||||
```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--
|
||||
|
||||
前回のチャレンジでは、2 つのコンポーネントを構成する簡単な方法を紹介しましたが、React でコンポーネントを構成する方法は他にもたくさんあります。
|
||||
|
||||
コンポーネントコンポジションは React の強力な機能の一つです。 React の作業では、前回のチャレンジの App の例のようなコンポーネントに関して、ユーザーインターフェイスの検討を始めることが重要です。 UI を基本的な構成要素に分解すると、それらの要素がコンポーネントになります。 この作業によって、アプリケーションロジックの処理を担うコードと UI を担うコードとを切り分けられるようになり、 複雑なプロジェクトの開発と保守を大幅に簡素化できます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに、`TypesOfFruit` と `Fruits` という 2 つの関数型コンポーネントが定義されています。 `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--
|
||||
|
||||
状況によっては、state を更新するときに以前の state を知る必要があります。 しかし、state の更新は非同期である可能性があります。これは React が複数の `setState()` 呼び出しを単一の更新にバッチ処理することを意味します。 つまり、次の値を計算するときに `this.state` または `this.props` の前の値に頼れない、ということです。 そのため、次のようなコードは使用しないでください。
|
||||
|
||||
```jsx
|
||||
this.setState({
|
||||
counter: this.state.counter + this.props.increment
|
||||
});
|
||||
```
|
||||
|
||||
代わりに、state や props にアクセスすることのできる関数を `setState` に渡してください。 `setState` で関数を使用することによって、state や props の最新の値を使用することが保証されます。 前の例は次のように書き換える必要があります。
|
||||
|
||||
```jsx
|
||||
this.setState((state, props) => ({
|
||||
counter: state.counter + props.increment
|
||||
}));
|
||||
```
|
||||
|
||||
`state` のみが必要な場合は、`props` のない形式も使用できます。
|
||||
|
||||
```jsx
|
||||
this.setState(state => ({
|
||||
counter: state.counter + 1
|
||||
}));
|
||||
```
|
||||
|
||||
オブジェクトリテラルを括弧で囲む必要があることに注意してください。そうしないと JavaScript によってコードのブロックであるとみなされます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`MyComponent` に `visibility` プロパティがあり、`false` に初期化されています。 `visibility` の値が true の場合、render メソッドはあるビューを返し、false の場合はそれとは別のビューを返します。
|
||||
|
||||
現在、コンポーネントの `state` の `visibility` プロパティを更新する方法はありません。 true と false の間で値を切り替える必要があります。 ボタンには、`toggleVisibility()` というクラスメソッドをトリガーする click ハンドラーがあります。 `setState` に関数を渡してこのメソッドを定義し、メソッドが呼び出されたときに `visibility` の `state` が反対の値に切り替わるようにしてください。 `visibility` が `false` の場合は、メソッドで `true` に設定します。その逆も同様です。
|
||||
|
||||
最後に、ボタンをクリックしたときに `state` に基づいてコンポーネントの条件付きレンダーの結果を表示してください。
|
||||
|
||||
**ヒント:** `this` キーワードを `constructor` のメソッドにバインドするのを忘れずに!
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` から、`button` を含む `div` 要素を返します。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MyComponent)).find('div').find('button')
|
||||
.length,
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` の state を初期化し、`visibility` プロパティを `false` に設定します。
|
||||
|
||||
```js
|
||||
assert.strictEqual(
|
||||
Enzyme.mount(React.createElement(MyComponent)).state('visibility'),
|
||||
false
|
||||
);
|
||||
```
|
||||
|
||||
ボタン要素をクリックしたときに、state の `visibility` プロパティを `true` と `false` の間で切り替えます。
|
||||
|
||||
```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--
|
||||
|
||||
ほとんどのウェブ開発者は、ある時点で、データを取得するために API エンドポイントを呼び出す必要があります。 React の作業では、このアクションをどこで実行すべきかを理解しておくことが重要です。
|
||||
|
||||
React では、API の呼び出しやサーバーへの呼び出しをライフサイクルメソッド `componentDidMount()` に配置することをお勧めします。 このメソッドは、コンポーネントが DOM にマウントされた後に呼び出されます。 この中で `setState()` を呼び出すと、そのたびにコンポーネントの再レンダーがトリガーされます。 このメソッドで API を呼び出して、API が返すデータを使用して state を設定すると、データの受信後に自動的に更新がトリガーされます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`componentDidMount()` に模擬的な API 呼び出しがあります。 この呼び出しは、2.5 秒後にサーバーの呼び出しをシミュレートしてデータを取得するように state を設定します。 この例では、サイトの現在のアクティブユーザーの合計数を要求します。 render メソッドで、`activeUsers` の値を `h1` のテキスト `Active Users:` の後にレンダーしてください。 プレビューで何が起きるかを確認し、タイムアウトを自由に変更してさまざまな効果を表示してください。
|
||||
|
||||
# --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 コンポーネントには、コンポーネントのライフサイクル中に特定の時点でアクションを実行する機会を提供する、いくつかの特別なメソッドがあります。 これらはライフサイクルメソッドまたはライフサイクルフックと呼ばれ、特定の時点でコンポーネントをキャッチできます。 メソッドは、レンダー前、更新前、prop を受け取る前、アンマウント前などの時点で実行できます。 主なライフサイクルメソッドとしては、`componentWillMount()`、`componentDidMount()`、`shouldComponentUpdate()`、`componentDidUpdate()`、`componentWillUnmount()` があります。以降のレッスンでは、これらのライフサイクルメソッドの基本的な用例について説明します。
|
||||
|
||||
**注:** ライフサイクルメソッド `componentWillMount` は、将来のバージョン 16.X で非推奨になり、バージョン 17 で削除される予定です [(情報元)](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html)。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`componentWillMount()` メソッドは、コンポーネントが DOM にマウントされるときに、`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 コンポーネントとして、`React.Component` を拡張する ES6 `class` があります。 このコンポーネントには、HTML (JSX からの) を返すかまたは `null` を返す render メソッドがあります。 これは React コンポーネントの基本的な形式です。 これについて十分に理解すれば、より複雑な React プロジェクトの構築を始めることができます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`React.Component` を拡張するクラス `MyComponent` を定義してください。 その render メソッドから、中に `My First React Component!` というテキストを持つ `h1` タグを含む `div` を返してください。 このテキストを正確に使用してください。大文字小文字の区別と句読点が重要です。 コンポーネントのコンストラクターも必ず呼び出してください。
|
||||
|
||||
`ReactDOM.render()` を使用してこのコンポーネントを DOM にレンダーしてください。 `id='challenge-node'` を持つ `div` を使用できます。
|
||||
|
||||
# --hints--
|
||||
|
||||
`MyComponent` という React コンポーネントを用意します。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
__helpers
|
||||
.removeWhiteSpace(getUserInput('index'))
|
||||
.includes('classMyComponentextendsReact.Component{')
|
||||
);
|
||||
```
|
||||
|
||||
`MyComponent` に、テキスト `My First React Component!` を持つ `h1` タグを含めます。大文字小文字の区別と句読点が重要です。
|
||||
|
||||
```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` を設定するメソッドの記述、それらのメソッドをトリガーする click ハンドラーの割り当てなどが必要になります。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`state` で `count` の値を追跡する `Counter` コンポーネントがあります。 2 つのボタンがあり、`increment()` メソッドと `decrement()` メソッドを呼び出します。 対応するボタンがクリックされたときにカウンターの値が 1 ずつ増加または減少するように、これらのメソッドを記述してください。 また、リセットボタンをクリックしたときにカウントを 0 に設定する `reset()` メソッドを作成してください。
|
||||
|
||||
**注:** ボタンの `className` は変更しないでください。 また、コンストラクターで新しく作成されるメソッドに必要なバインディングを忘れずに追加してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
`Counter` から、`Increment!`、`Decrement!`、`Reset` の 3 つのボタンをこのテキスト内容の順序で含む `div` 要素を返します。
|
||||
|
||||
```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'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`count` プロパティを `0` に設定して `Counter` の 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