chore(i18n,learn): processed translations (#44851)
This commit is contained in:
@ -0,0 +1,156 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036147
|
||||
title: Redux を React に接続する
|
||||
challengeType: 6
|
||||
forumTopicId: 301426
|
||||
dashedName: connect-redux-to-react
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`mapStateToProps()` 関数と `mapDispatchToProps()` 関数の両方を記述したので、これらを使用して `state` をマップし、いずれかの React コンポーネントの `props` に `dispatch` することができます。 React Redux の `connect` メソッドはこの処理を実行できます。 このメソッドは、`mapStateToProps()` と `mapDispatchToProps()` の 2 つのオプション引数を受け取ります。 これらがオプションになっているのは、`state` へのアクセスのみが必要でアクションをディスパッチする必要がない場合や、その逆の場合があるためです。
|
||||
|
||||
このメソッドを使用するには、関数を引数として渡し、直後にその結果をコンポーネントとともに呼び出します。 この構文は少し変わっていて、次のようになります。
|
||||
|
||||
```js
|
||||
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
|
||||
```
|
||||
|
||||
**注:** `connect` メソッドの引数の 1 つを省略したい場合は、代わりに `null` を渡します。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに `mapStateToProps()` 関数と `mapDispatchToProps()` 関数があり、`Presentational` という新しい React コンポーネントがあります。 `ReactRedux` グローバルオブジェクトの `connect` メソッドを使用して、このコンポーネントを Redux に接続し、直後に `Presentational` コンポーネントで呼び出してください。 その結果を、接続先のコンポーネントを表す `ConnectedComponent` という新しい `const` に割り当ててください。 これで Redux に接続します。 `connect` のいずれかの引数を `null` に変更して、テスト結果を確認してみてください。
|
||||
|
||||
# --hints--
|
||||
|
||||
`Presentational` コンポーネントをレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return mockedComponent.find('Presentational').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントで、`connect` を介して prop `messages` を受け取ります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const props = mockedComponent.find('Presentational').props();
|
||||
return props.messages === '__INITIAL__STATE__';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントで、`connect` を介して prop `submitNewMessage` を受け取ります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const props = mockedComponent.find('Presentational').props();
|
||||
return typeof props.submitNewMessage === 'function';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
const store = Redux.createStore(
|
||||
(state = '__INITIAL__STATE__', action) => state
|
||||
);
|
||||
class AppWrapper extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ReactRedux.Provider store = {store}>
|
||||
<ConnectedComponent/>
|
||||
</ReactRedux.Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
ReactDOM.render(<AppWrapper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
messages: state
|
||||
}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (message) => {
|
||||
dispatch(addMessage(message));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <h3>This is a Presentational Component</h3>
|
||||
}
|
||||
};
|
||||
|
||||
const connect = ReactRedux.connect;
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
messages: state
|
||||
}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (message) => {
|
||||
dispatch(addMessage(message));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <h3>This is a Presentational Component</h3>
|
||||
}
|
||||
};
|
||||
|
||||
const connect = ReactRedux.connect;
|
||||
// Change code below this line
|
||||
|
||||
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational);
|
||||
```
|
@ -0,0 +1,300 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036148
|
||||
title: Redux をメッセージアプリに接続する
|
||||
challengeType: 6
|
||||
forumTopicId: 301427
|
||||
dashedName: connect-redux-to-the-messages-app
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`connect` を使用して React を Redux に接続する方法を理解したところで、メッセージを処理する React コンポーネントに応用してみましょう。
|
||||
|
||||
前回のレッスンで、Redux に接続したコンポーネントは `Presentational` という名前でしたが、これは勝手に付けたものではありません。 この用語は*全般的に*、Redux に直接接続していない React コンポーネントのことを指します。 これらのコンポーネントは、単に UI の表現部分 (プレゼンテーション) を担い、自身が受け取る props の関数としてその機能を実行します。 これに対して、コンテナーコンポーネントは Redux に接続します。 これらは通常、ストアにアクションをディスパッチする役割を担い、多くの場合、ストアの state を props として子コンポーネントに渡します。
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターには、ここまでこのセクションで記述したすべてのコードがあります。 一つだけ、React コンポーネントの名前が `Presentational` に変更されています。 `Container` という定数に保持する新しいコンポーネントを作成してください。このコンポーネントは、`connect` を使用して `Presentational` コンポーネントを Redux に接続します。 次に、`AppWrapper` の中で React Redux の `Provider` コンポーネントをレンダーしてください。 `Provider` に Redux の `store` を prop として渡し、`Container` を子としてレンダーしてください。 すべての設定が完了すると、再びメッセージアプリがページにレンダーされます。
|
||||
|
||||
# --hints--
|
||||
|
||||
`AppWrapper` をページにレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return mockedComponent.find('AppWrapper').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントをページにレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return mockedComponent.find('Presentational').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントで、`h2`、`input`、`button`、`ul` の各要素をレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const PresentationalComponent = mockedComponent.find('Presentational');
|
||||
return (
|
||||
PresentationalComponent.find('div').length === 1 &&
|
||||
PresentationalComponent.find('h2').length === 1 &&
|
||||
PresentationalComponent.find('button').length === 1 &&
|
||||
PresentationalComponent.find('ul').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントで、Redux ストアから `messages` を prop として受け取ります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const PresentationalComponent = mockedComponent.find('Presentational');
|
||||
const props = PresentationalComponent.props();
|
||||
return Array.isArray(props.messages);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントで、`submitMessage` アクションクリエイターを prop として受け取ります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const PresentationalComponent = mockedComponent.find('Presentational');
|
||||
const props = PresentationalComponent.props();
|
||||
return typeof props.submitNewMessage === 'function';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<AppWrapper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
// Redux:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
this.setState((state) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.state.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// React-Redux:
|
||||
const mapStateToProps = (state) => {
|
||||
return { messages: state }
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (newMessage) => {
|
||||
dispatch(addMessage(newMessage))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
// Define the Container component here:
|
||||
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
// Complete the return statement:
|
||||
return (null);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
// Redux:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
this.setState((state) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.state.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// React-Redux:
|
||||
const mapStateToProps = (state) => {
|
||||
return { messages: state }
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (newMessage) => {
|
||||
dispatch(addMessage(newMessage))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Container/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,397 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036149
|
||||
title: ローカルの state を Redux に抽出する
|
||||
challengeType: 6
|
||||
forumTopicId: 301428
|
||||
dashedName: extract-local-state-into-redux
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
完成までもう少しです! Redux が React メッセージアプリの状態管理を制御できるように、すべての Redux コードを記述しました。 Redux に接続したので、`Presentational` コンポーネントから Redux に状態管理を抽出する必要があります。 現在は Redux に接続した状態ですが、`Presentational` コンポーネントの中で状態をローカルに処理しています。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`Presentational` コンポーネントで、まず、ローカルの `state` にある `messages` プロパティを削除してください。 これらのメッセージは Redux によって管理されます。 次に、`submitMessage()` メソッドを変更して `this.props` から `submitNewMessage()` をディスパッチするようにし、ローカルの `state` から入力された現在のメッセージを引数として渡してください。 ローカルの state から `messages` を削除したので、`this.setState()` 呼び出しからも `messages` プロパティを削除してください。 最後に、`render()` メソッドを変更して、`state` からではなく `props` から受信したメッセージをマップしてください。
|
||||
|
||||
以上の変更を加えると、Redux によって状態が管理されること以外、アプリの動作は引き続き同じです。 この例では、コンポーネントでローカルの `state` を維持できることも示しています。その場合、コンポーネントは引き続きユーザー入力を自身の `state` でローカルに追跡します。 Redux によって、React の上に便利な状態管理フレームワークが提供されることがわかります。 最初は React のローカルの state のみを使用して同じ結果を達成しました。シンプルなアプリであれば通常はそれが可能です。 しかし、アプリの規模が大きくなり複雑になるにつれて、状態管理もまた大きく複雑になります。そうした問題を Redux が解決してくれます。
|
||||
|
||||
# --hints--
|
||||
|
||||
`AppWrapper` をページにレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return mockedComponent.find('AppWrapper').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントをページにレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return mockedComponent.find('Presentational').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントで、`h2`、`input`、`button`、`ul` の各要素をレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const PresentationalComponent = mockedComponent.find('Presentational');
|
||||
return (
|
||||
PresentationalComponent.find('div').length === 1 &&
|
||||
PresentationalComponent.find('h2').length === 1 &&
|
||||
PresentationalComponent.find('button').length === 1 &&
|
||||
PresentationalComponent.find('ul').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントで、Redux ストアから `messages` を prop として受け取ります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const PresentationalComponent = mockedComponent.find('Presentational');
|
||||
const props = PresentationalComponent.props();
|
||||
return Array.isArray(props.messages);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントで、`submitMessage` アクションクリエイターを prop として受け取ります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const PresentationalComponent = mockedComponent.find('Presentational');
|
||||
const props = PresentationalComponent.props();
|
||||
return typeof props.submitNewMessage === 'function';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントの state に、空の文字列に初期化された 1 つのプロパティである `input` を含めます。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const PresentationalState = mockedComponent
|
||||
.find('Presentational')
|
||||
.instance().state;
|
||||
return (
|
||||
typeof PresentationalState.input === 'string' &&
|
||||
Object.keys(PresentationalState).length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`input` 要素への入力で、`Presentational` コンポーネントの state を更新します。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const testValue = '__MOCK__INPUT__';
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
let initialInput = mockedComponent.find('Presentational').find('input');
|
||||
const changed = () => {
|
||||
causeChange(mockedComponent, testValue);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated = await changed();
|
||||
const updatedInput = updated.find('Presentational').find('input');
|
||||
assert(
|
||||
initialInput.props().value === '' &&
|
||||
updatedInput.props().value === '__MOCK__INPUT__'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントでの `submitMessage` のディスパッチで、Redux ストアを更新し、ローカルの state の入力をクリアします。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
let beforeProps = mockedComponent.find('Presentational').props();
|
||||
const testValue = '__TEST__EVENT__INPUT__';
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const changed = () => {
|
||||
causeChange(mockedComponent, testValue);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const clickButton = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterChange = await changed();
|
||||
const afterChangeInput = afterChange.find('input').props().value;
|
||||
const afterClick = await clickButton();
|
||||
const afterProps = mockedComponent.find('Presentational').props();
|
||||
assert(
|
||||
beforeProps.messages.length === 0 &&
|
||||
afterChangeInput === testValue &&
|
||||
afterProps.messages.pop() === testValue &&
|
||||
afterClick.find('input').props().value === ''
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`Presentational` コンポーネントで、Redux ストアからの `messages` をレンダーします。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
let beforeProps = mockedComponent.find('Presentational').props();
|
||||
const testValue = '__TEST__EVENT__INPUT__';
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const changed = () => {
|
||||
causeChange(mockedComponent, testValue);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const clickButton = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterChange = await changed();
|
||||
const afterChangeInput = afterChange.find('input').props().value;
|
||||
const afterClick = await clickButton();
|
||||
const afterProps = mockedComponent.find('Presentational').props();
|
||||
assert(
|
||||
beforeProps.messages.length === 0 &&
|
||||
afterChangeInput === testValue &&
|
||||
afterProps.messages.pop() === testValue &&
|
||||
afterClick.find('input').props().value === '' &&
|
||||
afterClick.find('ul').childAt(0).text() === testValue
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<AppWrapper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
// Redux:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
// Change code below this line
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
this.setState((state) => ({
|
||||
input: '',
|
||||
messages: state.messages.concat(state.input)
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.state.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
// Change code above this line
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {messages: state}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (message) => {
|
||||
dispatch(addMessage(message))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Container/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
// Redux:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
// Change code below this line
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ''
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
this.props.submitNewMessage(this.state.input);
|
||||
this.setState({
|
||||
input: ''
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.props.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
// Change code above this line
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {messages: state}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (message) => {
|
||||
dispatch(addMessage(message))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Container/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,115 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036143
|
||||
title: 状態ロジックを Redux に抽出する
|
||||
challengeType: 6
|
||||
forumTopicId: 301429
|
||||
dashedName: extract-state-logic-to-redux
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
React コンポーネントが完成したので、`state` でローカルで実行しているロジックを Redux に移す必要があります。 この作業は、シンプルな React アプリケーションを Redux に接続するための最初のステップになります。 アプリで行う機能は、ユーザーからの新しいメッセージを順序なしリストに追加することだけです。 この例は React と Redux の連携動作を紹介する目的であるため、シンプルになっています。
|
||||
|
||||
# --instructions--
|
||||
|
||||
まず、アクションタイプ `ADD` を定義し、それを const `ADD` に設定してください。 次に、メッセージを追加するためのアクションを作成するアクションクリエイター `addMessage()` を定義してください。 このアクションクリエイターに `message` を渡し、返された `action` にメッセージを含める必要があります。
|
||||
|
||||
その次に、メッセージの状態を処理する `messageReducer()` というレデューサーを作成してください。 初期状態は空の配列に等しくしてください。 このレデューサーでは、state に保持されているメッセージの配列にメッセージを追加するか、または現在の state を返す必要があります。 最後に、Redux ストアを作成してレデューサーを渡してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
const `ADD` が存在し、文字列 `ADD` に等しい値を保持する必要があります。
|
||||
|
||||
```js
|
||||
assert(ADD === 'ADD');
|
||||
```
|
||||
|
||||
アクションクリエイター `addMessage` から、`type` が `ADD` に等しく、`message` が渡されたメッセージに等しいオブジェクトを返します。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const addAction = addMessage('__TEST__MESSAGE__');
|
||||
return addAction.type === ADD && addAction.message === '__TEST__MESSAGE__';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`messageReducer` は関数である必要があります。
|
||||
|
||||
```js
|
||||
assert(typeof messageReducer === 'function');
|
||||
```
|
||||
|
||||
ストアが存在し、初期状態が空の配列に設定されている必要があります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
return typeof store === 'object' && initialState.length === 0;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
ストアに対する `addMessage` のディスパッチで、state に保持されているメッセージの配列に、新しいメッセージをイミュータブルに追加します。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
const isFrozen = DeepFreeze(initialState);
|
||||
store.dispatch(addMessage('__A__TEST__MESSAGE'));
|
||||
const addState = store.getState();
|
||||
return isFrozen && addState[0] === '__A__TEST__MESSAGE';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`messageReducer` は、他のアクションで呼び出された場合に、現在の state を返す必要があります。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const addState = store.getState();
|
||||
store.dispatch({ type: 'FAKE_ACTION' });
|
||||
const testState = store.getState();
|
||||
return addState === testState;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
// Define ADD, addMessage(), messageReducer(), and store here:
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
```
|
@ -0,0 +1,102 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036141
|
||||
title: React Redux 入門
|
||||
challengeType: 6
|
||||
forumTopicId: 301430
|
||||
dashedName: getting-started-with-react-redux
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
この一連のチャレンジでは、React で Redux を使用する方法を紹介します。 最初に、それぞれのテクノロジーの主な原則についていくつか確認しておきましょう。 React はビューライブラリです。データを提供すると、効率的で予測可能な方法でビューをレンダーします。 Redux は状態管理フレームワークです。これを使用してアプリケーションの状態の管理を簡素化することができます。 通常、React Redux アプリでは、アプリ全体の状態を管理する単一の Redux ストアを作成します。 React コンポーネントでは、ストア内のデータのうち、それらの役割に関連するもののみにサブスクライブします。 そして、React コンポーネントから直接アクションをディスパッチすることで、ストアの更新をトリガーします。
|
||||
|
||||
React コンポーネントで自身の状態をローカルに管理することもできますが、複雑なアプリの場合は、Redux を使用してアプリの状態を 1 か所に保つ方が通常は便利です。 ただし、個々のコンポーネントが自身に特有のローカルな状態を持つような場合は例外です。 また、Redux はそのままで React と連携するようには設計されていないため、`react-redux` パッケージを使用する必要があります。 このパッケージによって、React コンポーネントに Redux の `state` を渡して `props` として `dispatch` できるようになります。
|
||||
|
||||
以降のいくつかのチャレンジでは、まず、新しいテキストメッセージを入力できるシンプルな React コンポーネントを作成します。 メッセージは配列に追加され、ビューに表示されます。 このチャレンジは React のレッスンで学んだことを復習する良い機会になります。 次に、messages 配列の状態を管理する Redux ストアとアクションを作成します。 最後に、`react-redux` を使用して Redux ストアとコンポーネントを接続し、それを通じてローカルの state を Redux ストアに抽出します。
|
||||
|
||||
# --instructions--
|
||||
|
||||
`DisplayMessages` コンポーネントから作業を始めます。 このコンポーネントにコンストラクターを追加し、2 つのプロパティを持つ state で初期化してください。プロパティの 1 つは `input` で、空の文字列に設定します。もう 1 つは `messages` で、空の配列に設定します。
|
||||
|
||||
# --hints--
|
||||
|
||||
`DisplayMessages` コンポーネントで空の `div` 要素をレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
return mockedComponent.find('div').text() === '';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`DisplayMessages` コンストラクターを `super` を使用して正しく呼び出し、`props` を渡します。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
const noWhiteSpace = __helpers.removeWhiteSpace(getUserInput('index'));
|
||||
return (
|
||||
noWhiteSpace.includes('constructor(props)') &&
|
||||
noWhiteSpace.includes('super(props')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`DisplayMessages` コンポーネントの初期状態を `{input: "", messages: []}` にします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const initialState = mockedComponent.state();
|
||||
return (
|
||||
typeof initialState === 'object' &&
|
||||
initialState.input === '' &&
|
||||
Array.isArray(initialState.messages) &&
|
||||
initialState.messages.length === 0
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<DisplayMessages />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class DisplayMessages extends React.Component {
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
render() {
|
||||
return <div />
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return <div/>
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,260 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036142
|
||||
title: 最初に state をローカルに管理する
|
||||
challengeType: 6
|
||||
forumTopicId: 301431
|
||||
dashedName: manage-state-locally-first
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
ここでは、`DisplayMessages` コンポーネントの作成を完了させます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
まず、`render()` メソッドで、コンポーネントに `input` 要素、`button` 要素、および `ul` 要素をレンダーさせてください。 `input` 要素が変更されたときは、`handleChange()` メソッドをトリガーする必要があります。 また、`input` 要素で、コンポーネントの state にある `input` の値をレンダーしてください。 `button` 要素がクリックされたときは、`submitMessage()` メソッドをトリガーする必要があります。
|
||||
|
||||
次に、2 つのメソッドを記述してください。 `handleChange()` メソッドでは、`input` を、ユーザーが入力している内容に更新してください。 `submitMessage()` メソッドでは、現在のメッセージ (`input` に格納されています) をローカル state の `messages` 配列に結合し、`input` の値をクリアしてください。
|
||||
|
||||
最後に、`ul` を使用して `messages` の配列をマップし、`li` 要素のリストとして画面にレンダーしてください。
|
||||
|
||||
# --hints--
|
||||
|
||||
`DisplayMessages` コンポーネントを初期化し、state を `{ input: "", messages: [] }` にします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const initialState = mockedComponent.state();
|
||||
return (
|
||||
typeof initialState === 'object' &&
|
||||
initialState.input === '' &&
|
||||
initialState.messages.length === 0
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`DisplayMessages` コンポーネントで、`h2` 要素、`button` 要素、 `ul` 要素、および `li` 要素を子として含む `div` をレンダーします。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const state = () => {
|
||||
mockedComponent.setState({ messages: ['__TEST__MESSAGE'] });
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated = await state();
|
||||
assert(
|
||||
updated.find('div').length === 1 &&
|
||||
updated.find('h2').length === 1 &&
|
||||
updated.find('button').length === 1 &&
|
||||
updated.find('ul').length === 1 &&
|
||||
updated.find('li').length > 0
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`messages` 配列で `.map` を使用します。
|
||||
|
||||
```js
|
||||
assert(code.match(/this\.state\.messages\.map/g));
|
||||
```
|
||||
|
||||
`input` 要素で、ローカルの state にある `input` の値をレンダーします。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const testValue = '__TEST__EVENT__INPUT';
|
||||
const changed = () => {
|
||||
causeChange(mockedComponent, testValue);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated = await changed();
|
||||
assert(updated.find('input').props().value === testValue);
|
||||
};
|
||||
```
|
||||
|
||||
メソッド `handleChange` の呼び出しで、`input` の値を現在の入力に更新します。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const initialState = mockedComponent.state();
|
||||
const testMessage = '__TEST__EVENT__MESSAGE__';
|
||||
const changed = () => {
|
||||
causeChange(mockedComponent, testMessage);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterInput = await changed();
|
||||
assert(
|
||||
initialState.input === '' &&
|
||||
afterInput.state().input === '__TEST__EVENT__MESSAGE__'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`Add message` ボタンのクリックで、メソッド `submitMessage` を呼び出します。このメソッドは、現在の `input` を state 内の `messages` 配列に追加します。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const initialState = mockedComponent.state();
|
||||
const testMessage_1 = '__FIRST__MESSAGE__';
|
||||
const firstChange = () => {
|
||||
causeChange(mockedComponent, testMessage_1);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const firstResult = await firstChange();
|
||||
const firstSubmit = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterSubmit_1 = await firstSubmit();
|
||||
const submitState_1 = afterSubmit_1.state();
|
||||
const testMessage_2 = '__SECOND__MESSAGE__';
|
||||
const secondChange = () => {
|
||||
causeChange(mockedComponent, testMessage_2);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const secondResult = await secondChange();
|
||||
const secondSubmit = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterSubmit_2 = await secondSubmit();
|
||||
const submitState_2 = afterSubmit_2.state();
|
||||
assert(
|
||||
initialState.messages.length === 0 &&
|
||||
submitState_1.messages.length === 1 &&
|
||||
submitState_2.messages.length === 2 &&
|
||||
submitState_2.messages[1] === testMessage_2
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`submitMessage` メソッドで、現在の入力をクリアします。
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const initialState = mockedComponent.state();
|
||||
const testMessage = '__FIRST__MESSAGE__';
|
||||
const firstChange = () => {
|
||||
causeChange(mockedComponent, testMessage);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const firstResult = await firstChange();
|
||||
const firstState = firstResult.state();
|
||||
const firstSubmit = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterSubmit = await firstSubmit();
|
||||
const submitState = afterSubmit.state();
|
||||
assert(firstState.input === testMessage && submitState.input === '');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<DisplayMessages />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
}
|
||||
// Add handleChange() and submitMessage() methods here
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
{ /* Render an input, button, and ul below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
this.setState((state) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.state.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,107 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036146
|
||||
title: dispatch を props にマップする
|
||||
challengeType: 6
|
||||
forumTopicId: 301432
|
||||
dashedName: map-dispatch-to-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`mapDispatchToProps()` は、React コンポーネントに特定のアクションクリエイターを提供するために使用する関数で、アクションクリエイターから Redux ストアに対してアクションをディスパッチできるようにします。 この関数は、前回のチャレンジで記述した `mapStateToProps()` 関数と構造が似ていますが、 ディスパッチアクションをプロパティ名にマップするオブジェクトを返し、それがコンポーネントの `props` になります。 ただし、`state` の一部分を返すのではなく、各プロパティから、アクションクリエイターと関連するアクションデータ (存在する場合) を使用して `dispatch` を呼び出す関数を返します。 この `dispatch` は、`state` を `mapStateToProps()` に渡したのとまったく同じように、関数を定義するときにパラメーターとして `mapDispatchToProps()` に渡されます。そのため、dispatch にアクセスすることができます。 バックグラウンドでは、React Redux が `mapDispatchToProps()` でこれらのディスパッチを実行するために、Redux の `store.dispatch()` を使用しています。 これは、`state` にマッピングされたコンポーネントに対して `store.subscribe()` を使用する方法に似ています。
|
||||
|
||||
たとえば、`loginUser()` というアクションクリエイターがあり、アクションペイロードとして `username` を受け取るとします。 このアクションクリエイターの `mapDispatchToProps()` から返されるオブジェクトは次のようになります。
|
||||
|
||||
```jsx
|
||||
{
|
||||
submitLoginUser: function(username) {
|
||||
dispatch(loginUser(username));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに `addMessage()` というアクションクリエイターが用意されています。 `dispatch` を引数として受け取り、オブジェクトを返す、関数 `mapDispatchToProps()` を記述してください。 オブジェクトには、dispatch 関数に設定されたプロパティ `submitNewMessage` を持たせます。この関数は、`addMessage()` をディスパッチするときに新しいメッセージを追加するためのパラメーターを受け取ります。
|
||||
|
||||
# --hints--
|
||||
|
||||
`addMessage` から、`type` と `message` というキーを持つオブジェクトを返します。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const addMessageTest = addMessage();
|
||||
return (
|
||||
addMessageTest.hasOwnProperty('type') &&
|
||||
addMessageTest.hasOwnProperty('message')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`mapDispatchToProps` は関数である必要があります。
|
||||
|
||||
```js
|
||||
assert(typeof mapDispatchToProps === 'function');
|
||||
```
|
||||
|
||||
`mapDispatchToProps` はオブジェクトを返す必要があります。
|
||||
|
||||
```js
|
||||
assert(typeof mapDispatchToProps() === 'object');
|
||||
```
|
||||
|
||||
`mapDispatchToProps` からの `submitNewMessage` による `addMessage` のディスパッチで、dispatch 関数にメッセージを返します。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
let testAction;
|
||||
const dispatch = (fn) => {
|
||||
testAction = fn;
|
||||
};
|
||||
let dispatchFn = mapDispatchToProps(dispatch);
|
||||
dispatchFn.submitNewMessage('__TEST__MESSAGE__');
|
||||
return (
|
||||
testAction.type === 'ADD' && testAction.message === '__TEST__MESSAGE__'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: function(message) {
|
||||
dispatch(addMessage(message));
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,69 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036145
|
||||
title: state を props にマップする
|
||||
challengeType: 6
|
||||
forumTopicId: 301433
|
||||
dashedName: map-state-to-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`Provider` コンポーネントを使用すると、React コンポーネントに `state` と `dispatch` を提供することができます。ただし、どのような状態とアクションが必要なのかを正確に指定する必要があります。 そうすることで、各コンポーネントから必要な state にのみアクセスできるようになります。 これを実現するには、`mapStateToProps()` と `mapDispatchToProps()` の 2 つの関数を作成します。
|
||||
|
||||
これらの関数では、state のどの部分にアクセスしたいのか、そして、どのアクションクリエイターをディスパッチできるようにする必要があるのかを宣言します。 これらの関数を用意できたら、別のチャレンジで React Redux の `connect` メソッドを使用してそれらをコンポーネントに接続する方法を試すことができます。
|
||||
|
||||
**注:** バックグラウンドでは、React Redux は `store.subscribe()` メソッドを使用して `mapStateToProps()` を実装します。
|
||||
|
||||
# --instructions--
|
||||
|
||||
関数 `mapStateToProps()` を作成してください。 この関数は、引数として `state` を受け取り、その state を特定のプロパティ名にマップするオブジェクトを返します。 これらのプロパティには `props` を介してコンポーネントからアクセスできるようになります。 この例では、アプリケーションの state 全体を単一の配列に保持するため、コンポーネントにその state 全体を渡すことができます。 返されるオブジェクトにプロパティ `messages` を作成し、`state` に設定してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
const `state` は空の配列である必要があります。
|
||||
|
||||
```js
|
||||
assert(Array.isArray(state) && state.length === 0);
|
||||
```
|
||||
|
||||
`mapStateToProps` は関数である必要があります。
|
||||
|
||||
```js
|
||||
assert(typeof mapStateToProps === 'function');
|
||||
```
|
||||
|
||||
`mapStateToProps` はオブジェクトを返す必要があります。
|
||||
|
||||
```js
|
||||
assert(typeof mapStateToProps() === 'object');
|
||||
```
|
||||
|
||||
配列を state として `mapStateToProps` に渡すと、`messages` のキーに割り当てられたこの配列を返します。
|
||||
|
||||
```js
|
||||
assert(mapStateToProps(['messages']).messages.pop() === 'messages');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const state = [];
|
||||
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const state = [];
|
||||
|
||||
// Change code below this line
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
messages: state
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,69 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403614a
|
||||
title: 次に進む
|
||||
challengeType: 6
|
||||
forumTopicId: 301434
|
||||
dashedName: moving-forward-from-here
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
おめでとうございます ! React と Redux のレッスンが終了しました。 次に進む前に、最後に重要なことを一つ説明しておきましょう。 通常はこのようなコードエディターで React アプリケーションを記述することはありません。 このチャレンジでは、皆さん自身のマシンで npm とファイルシステムを使用する場合にどのような構文になるかを少しだけ紹介します。 コードは、`import` ステートメントを使用している部分を除いて似たものになります (これらの import はチャレンジで提供したすべての依存関係を抽出します)。 npm については「npm でパッケージを管理する」セクションで詳しく説明します。
|
||||
|
||||
最後に、React と Redux のコードを記述する際、通常はいくつかの設定が必要になります。 この設定はすぐに複雑になる可能性があります。 自分のマシンで試してみたい方は、「<a href="https://github.com/facebookincubator/create-react-app" target="_blank" rel="nofollow">React アプリを作成する</a>」で設定と準備を行ってください。
|
||||
|
||||
または、CodePen で JavaScript プリプロセッサーとして Babel を有効にし、React と ReactDOM を外部の JavaScript リソースとして追加し、そこで作業することもできます。
|
||||
|
||||
# --instructions--
|
||||
|
||||
メッセージ `'Now I know React and Redux!'` をコンソールに出力してください。
|
||||
|
||||
# --hints--
|
||||
|
||||
メッセージ `Now I know React and Redux!` をコンソールに出力します。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
/console\s*\.\s*log\s*\(\s*('|"|`)Now I know React and Redux!\1\s*\)/.test(
|
||||
getUserInput('index')
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
/*
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider, connect } from 'react-redux'
|
||||
import { createStore, combineReducers, applyMiddleware } from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
|
||||
import rootReducer from './redux/reducers'
|
||||
import App from './components/App'
|
||||
|
||||
const store = createStore(
|
||||
rootReducer,
|
||||
applyMiddleware(thunk)
|
||||
);
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App/>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
*/
|
||||
|
||||
// Only change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
console.log('Now I know React and Redux!');
|
||||
```
|
@ -0,0 +1,263 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036144
|
||||
title: Provider を使用して Redux を React に接続する
|
||||
challengeType: 6
|
||||
forumTopicId: 301435
|
||||
dashedName: use-provider-to-connect-redux-to-react
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
前回のチャレンジでは、messages 配列を処理するための Redux ストアを作成し、新しいメッセージを追加するためのアクションを作成しました。 次のステップとして、React から Redux ストアにアクセスできるようにし、更新のディスパッチに必要なアクションにアクセスできるようにします。 React Redux にはこの作業に役立つ `react-redux` というパッケージが用意されています。
|
||||
|
||||
React Redux には、2 つの重要な機能を実行する、`Provider` と `connect` という小さな API があります。 `connect` については別のチャレンジで説明します。 `Provider` は、React アプリケーションをラップする React Redux のラッパーコンポーネントです。 このラッパーを使用すると、コンポーネントツリー全体にわたって Redux の `store` 関数と `dispatch` 関数にアクセスできます。 `Provider` は、Redux ストアとアプリケーションの子コンポーネントの 2 つのプロパティを受け取ります。 たとえば、App コンポーネントの `Provider` の定義は次のようになります。
|
||||
|
||||
```jsx
|
||||
<Provider store={store}>
|
||||
<App/>
|
||||
</Provider>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
コードエディターに、前のいくつかのチャレンジで紹介した Redux コードと React コードがあります。 これには Redux のストア、アクション、`DisplayMessages` コンポーネントが含まれています。 唯一の新しいコードは、一番下にある `AppWrapper` コンポーネントです。 この最上位のコンポーネントを使用して、`ReactRedux` から `Provider` をレンダーし、Redux ストアを prop として渡してください。 次に、`DisplayMessages` コンポーネントを子としてレンダーしてください。 作業が完了すると、React コンポーネントがページにレンダーされます。
|
||||
|
||||
**注:** React Redux はここではグローバル変数として利用できるので、Provider にはドット表記でアクセスできます。 エディター内のコードではこれを利用しており、定数 `Provider` に設定して、`AppWrapper` の render メソッドで使用できるようにしています。
|
||||
|
||||
# --hints--
|
||||
|
||||
`AppWrapper` をレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return mockedComponent.find('AppWrapper').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`Provider` ラッパーコンポーネントで、 `store` という prop を渡し、Redux ストアに等しく設定します。
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return __helpers
|
||||
.removeWhiteSpace(getUserInput('index'))
|
||||
.includes('<Providerstore={store}>');
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`DisplayMessages` を `AppWrapper` の子としてレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return (
|
||||
mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`DisplayMessages` コンポーネントで、`h2`、`input`、`button`、`ul` の各要素をレンダーします。
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return (
|
||||
mockedComponent.find('div').length === 1 &&
|
||||
mockedComponent.find('h2').length === 1 &&
|
||||
mockedComponent.find('button').length === 1 &&
|
||||
mockedComponent.find('ul').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<AppWrapper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
// Redux:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
this.setState((state) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.state.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
// Render the Provider below this line
|
||||
|
||||
// Change code above this line
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
// Redux:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
this.setState((state) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.state.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
// Change code below this line
|
||||
render() {
|
||||
return (
|
||||
<Provider store = {store}>
|
||||
<DisplayMessages/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
// Change code above this line
|
||||
};
|
||||
```
|
Reference in New Issue
Block a user