chore(i18n,curriculum): processed translations - new ukrainian (#44447)
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` і `dispatch` до `props` одного з ваших компонентів React. `connect` метод з React Redux може обробити це завдання. Цей метод вимагає/використовує два необов'язкових параметри, `mapStateToProps()` та `mapDispatchToProps()`. Вони не є обов'язковими, оскільки у вас може бути компонент, який лише потребує доступу до `state` але не потребує виконання жодних дій, або навпаки.
|
||||
|
||||
Щоб використати цей метод, передайте функції у якості аргументів і одразу викликайте результат з вашим компонентом. Цей синтаксис є трохи незвичним і виглядає наступним чином:
|
||||
|
||||
```js
|
||||
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
|
||||
```
|
||||
|
||||
**Note:** Якщо ви хочете пропустити один з параметрів замість `connect` методу, ви передаєте `null` замість нього.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Редактор коду має функції `mapStateToProps()` та `mapDispatchToProps()` і новий компонент React під назвою `Presentational`. З'єднайте цей компонент з Redux за допомогою методу `connect` з глобального об'єкту `ReactRedux` і одразу викличте його в компоненті `Presentational`. Призначте результат новому `const` під назвою `ConnectedComponent`, який представляє підключений елемент. Ось і все, тепер ви підключені до Redux! Спробуйте змінити будь-який з параметрів `connect` на `null` і простежте за результатами тестів.
|
||||
|
||||
# --hints--
|
||||
|
||||
Компонент `Presentational` повинен відображатись.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return mockedComponent.find('Presentational').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
Компонент `Presentational` повинен отримати пропс `messages` через `connect`.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const props = mockedComponent.find('Presentational').props();
|
||||
return props.messages === '__INITIAL__STATE__';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
Компонент `Presentational` повинен отримати пропс `submitNewMessage` через `connect`.
|
||||
|
||||
```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` і це не було довільним. Цей термін *зазвичай* посилається на компоненти React, які не підключені безпосередньо до Redux. Вони просто відповідають за представлення інтерфейсу користувача і виконують це в залежності від функції пропсів, які вони отримують. На відміну від цього, компонент container пов'язаний із Redux. Вони, зазвичай є відповідальними за відправлення дій у сховище даних і часто передають store state до дочірніх компонентів у якості пропсів.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Редактор коду вже містить весь написаний вами код у цій секції. Єдина зміна полягає у тому, що компонент React перейменовується на `Presentational`. Створіть новий компонент, який знаходиться у константі під назвою `Container`, який використовує `connect`, щоб під'єднати компонент `Presentational` до Redux. Потім, у `AppWrapper`, відобразіть React Redux компонент `Provider`. Передайте `Provider` Redux `store` у якості пропсу і відобразіть `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` повинен отримувати `messages` зі сховища Redux у якості пропсу.
|
||||
|
||||
```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` виконавця як пропс.
|
||||
|
||||
```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: Вилучення Local State в Redux
|
||||
challengeType: 6
|
||||
forumTopicId: 301428
|
||||
dashedName: extract-local-state-into-redux
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Майже готово! Пам'ятайте, що ви написали весь код Redx, так щоб Redux міг контролювати управління станом вашого додатка повідомлень в React. Тепер, коли Redux підключений, вам потрібно витягти управління станами з компонента `Presentational` і перемістити його в Redux. У даний момент, ваш Redux підключений, але ви обробляєте стан локально всередині компонента `Presentational`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
В компоненті `Presentational`, спершу, видаліть значення `messages` в локальному стані `state`. Управління повідомленнями буде здійснюватись в Redux. Далі, змініть метод `submitMessage()`, так щоб він відправляв `submitNewMessage()` з `this.props` і передайте йому поточний ввід повідомлень з локального `state` як аргумент. Через те, що ви видалили `messages` з локального стану, видаліть властивість/значення `messages` з виклику до `this.setState()` також. Нарешті, змініть метод `render()`, так щоб він відображав повідомлення отримані з `props`, а не з `state`.
|
||||
|
||||
Як тільки ці зміни будуть внесені, застосунок продовжить функціонувати так само, за винятком того, що тепер Redux керує станом. Цей приклад також демонструє, як компонент може мати локальний `state`: ваш компонент все ще відслідковує вхідні дані користувача локально у своєму власному `state`. Ви можете побачити, як Redux надає зручний фреймворк управління станом на основі React. Ви досягли того ж результату використовуючи тільки локальний стан React спершу і це, зазвичай, можливо з простими додатками. Однак, в той час як ваші додатки ставатимуть масштабнішими і складнішими, і ваше управління станом зазнаватиме тих же змін, саме цю проблему вирішуватиме 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` повинен отримувати `messages` зі сховища Redux у якості пропсу.
|
||||
|
||||
```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` виконавця як пропс.
|
||||
|
||||
```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` повинен містити одну властивість/значення, `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`.
|
||||
|
||||
```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__'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Відправлення `submitMessage` на компонент `Presentational` має оновити Redux сховище і очистити вхідні дані у локальному стані.
|
||||
|
||||
```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` має відображати `messages` зі сховища Redux.
|
||||
|
||||
```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: Вилучення State Logic в 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()`, яке обробляє стан для повідомлень. Початковий стан повинен дорівнювати пустому масиву. Це скорочення повинно додавати повідомлення до масиву, який зберігається у стані чи повертати поточний стан. Нарешті, створіть ваше сховище Redux та передайте його до reducer.
|
||||
|
||||
# --hints--
|
||||
|
||||
Конст `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` до сховища повинно незмінно додавати нове повідомлення до масиву повідомлень, які знаходиться у стані.
|
||||
|
||||
```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` має повернутися до поточного стану, якщо викликається з будь-яким іншими діями.
|
||||
|
||||
```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--
|
||||
|
||||
Ця низка завдань показує те, як можна використовувати Redux з React. По-перше, ось огляд деяких ключових принципів кожної технології. React - це бібліотека, якій ви надаєте дані. Потім вона візуалізує перегляд у більш ефективний, передбачуваний спосіб. Redux - це система управління станом, яку ви можете використовувати для спрощення управління станом вашого застосунку. Зазвичай, у додатку React Redux, ви створюєте одне сховище Redux, яке керує станом усього застосунку. Ваші компоненти React підписуються лише на ті частини даних в сховищі, що мають відношення до їхньої ролі. Потім ви відправляєте дії безпосередньо від компонентів React, які потім запускають оновлення сховища.
|
||||
|
||||
Хоча компоненти React можуть локально керувати своїм станом, коли у вас є складний застосунок, зазвичай, краще зберігати стан застосунку в одному місці за допомогою Redux. Існують винятки, коли окремі компоненти можуть мати локальний стан, створений конкретно лише для них. Зрештою, через те, що Redux не створений для нестандартної роботи з React, вам потрібно використовувати пакет `react-redux`. Він надає вам можливість передавати Redux `state` та `dispatch` вашим компонентам React як `props`.
|
||||
|
||||
Перш за все, у декількох наступних завданнях ви створите простий компонент React, який дозволить вам вводити нові повідомлення. Вони додаються до масиву, що відображається в огляді. Це має бути хороший огляд того, що ви вивчили на уроках React. Далі ви створите сховище Redux та дії, які керують станом масиву повідомлень. Зрештою, ви використовуватимете `react-redux` для того, щоб з'єднати сховище Redux з вашим компонентом, таким чином додаючи локальний стан в сховище Redux.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Почніть з компоненту `DisplayMessages`. Додайте конструктора до цього компонента та ініціалізуйте його за допомогою двох параметрів: `input`, це значення задається у порожньому рядку і `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: Спочатку керуйте станом локально
|
||||
challengeType: 6
|
||||
forumTopicId: 301431
|
||||
dashedName: manage-state-locally-first
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Тут ви закінчите створення компонента `DisplayMessages`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Спершу в методі `render()` відобразіть елементи `input`, `button`, та `ul`. Коли елемент `input` змінюється, він повинен запускати метод `handleChange()`. Також елемент `input` повинен відображати значення `input`, що знаходиться в стані компоненту. При натисканні елемент `button` повинен запускати метод `submitMessage()`.
|
||||
|
||||
По-друге, запишіть ці два методи. Метод `handleChange()` повинен оновлювати `input` з тим, що друкує користувач. Метод `submitMessage()` повинен об'єднати поточне повідомлення (зберігається в `input`) та масив `messages` в локальному стані та очистити значення `input`.
|
||||
|
||||
Зрештою, використовуйте `ul`, щоб окреслити масив `messages` та відобразити його на екрані як список елементів `li`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Компонент `DisplayMessages` повинен ініціалізуватися за допомогою початкового стану до `{ 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` повинен відображати `div`, що містить елементи `h2`, `button`, `ul`, та `li` elements.
|
||||
|
||||
```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
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`.map` повинен використовуватися в масиві `messages`.
|
||||
|
||||
```js
|
||||
assert(code.match(/this\.state\.messages\.map/g));
|
||||
```
|
||||
|
||||
Елемент `input` повинен відображати значення `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` до масиву `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`, тому що він передається в `mapDispatchToProps()` як параметр, коли ви визначаєте функцію, так само, як ви пройшли від `state` до `mapStateToProps()`. По секрету, React Redux використовує Redux `store.dispatch()` для проведення цих відправлень за допомогою `mapDispatchToProps()`. Це схоже на те, як він використовує `store.subscribe()` для компонентів, зіставлених на `state`.
|
||||
|
||||
Наприклад, у вас є творець дій `loginUser()`, який приймає `username`, як корисну дію. Об'єкт, повернений з `mapDispatchToProps()` для цього творця дій виглядатиме приблизно так:
|
||||
|
||||
```jsx
|
||||
{
|
||||
submitLoginUser: function(username) {
|
||||
dispatch(loginUser(username));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Редактор коду надає творця дії, який називається `addMessage()`. Запишіть функцію `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');
|
||||
```
|
||||
|
||||
Надсилання `addMessage` разом з `submitNewMessage` від `mapDispatchToProps` повинне повернути повідомлення до функції відправлення.
|
||||
|
||||
```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--
|
||||
|
||||
The `Provider` дозволяє забезпечення надходження `state` і `dispatch` до компонентів React, але ви повинні точно вказати, який state та дії ви хочете. Таким чином, ви переконаєтесь, що кожен компонент має доступ лише до state, який йому потрібен. Ви досягнете цього, створюючи дві функції: `mapStateToProps()` і `mapDispatchToProps()`.
|
||||
|
||||
У цих функціях ви декларуєте, до яких частин state ви хочете мати доступ і до яких авторів дій потрібно надіслати. Після того, як ці функції будуть встановлені, ви побачите, як за допомогою методу React Redux `connect` з'єднати їх зі своїми компонентами в іншому виклику.
|
||||
|
||||
**Note:**За межами React Redux використовується метод `store.subscribe()` для реалізації `mapStateToProps()`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Створіть функцію `mapStateToProps()`. Ця функція повинна приймати `state` в якості аргумента, а потім повертати об’єкт, який відображає цей стан з конкретними іменами властивостей. Ці властивості стають доступними для компонента за допомогою `props`. З того моменту коли в прикладі зберігається весь state програми в одному масиві, ви можете передати весь цей state своєму компоненту. Створіть властивість `messages` в об’єкті, який повертається, і встановіть для нього `state`.
|
||||
|
||||
# --hints--
|
||||
|
||||
The 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` (які викличуть усі залежності, які були передбачені для вас у завданнях). Розділ "Керування пакунками з npm" містить детальнішу інформацію про npm.
|
||||
|
||||
Зрештою, написання коду React і Redux зазвичай вимагає певного налаштування. Це може швидко ускладнитись. Якщо вам цікаво експериментувати на власному пристрої, <a href="https://github.com/facebookincubator/create-react-app" target="_blank" rel="nofollow">Create React App</a> вже є налаштованим та готовим до роботи.
|
||||
|
||||
Крім того, ви можете включити Babel як JavaScript Preprocessor в CodePen, додати 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: Використовуйте Провайдер для підключення Redux до React
|
||||
challengeType: 6
|
||||
forumTopicId: 301435
|
||||
dashedName: use-provider-to-connect-redux-to-react
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
В останньому завданні ви створили сховище Redux для обробки масиву повідомлень і створили дію для додавання нових повідомлень. Наступний крок - надати React доступ до сховища Redux і дії, необхідні для відправки оновлень. React Redux забезпечує свій пакет `react-redux` для допомоги виконання цих завдань.
|
||||
|
||||
React Redux надає невеличкий API з двома основними особливостями:`Provider` і `connect`. Інша проблема стосується `connect`. `Provider` - пакувальний компонент з React Redux, який обгортає додаток React. Ця обгортка дозволяє вам отримати доступ до сховища Redux `store` і `dispatch` функцій у вашому дереві компонентів. `Provider` приймає два відгуки, сховище Redux і дочірні компоненти вашої програми. Визначення `Provider` для компонента програми може виглядати так:
|
||||
|
||||
```jsx
|
||||
<Provider store={store}>
|
||||
<App/>
|
||||
</Provider>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Редактор коду тепер показує всі коди Redux і React з минулих завдань. Він включає в себе сховище Redx, дії і компонент `DisplayMessages`. Єдина нова частина є компонентом `AppWrapper` унизу(AppWrapp). Використовувати цей компонент верхнього рівня для відображення `Provider` з `ReactRedux`і передати магазин Redux як пропс. Потім надайте `DisplayMessages`компонента як дочірного. Як тільки ви закінчите, ви повинні побачити компонент React, що відображається на сторінці.
|
||||
|
||||
**Примітка:** React Redux доступна як глобальна змінна тут, так що ви можете отримати доступ до провайдера з точковою нотацією. Код в редакторі використовує переваги і встановлює константу `Provider` для використання в методі render `AppWrapper`.
|
||||
|
||||
# --hints--
|
||||
|
||||
`AppWrapper` повинен візуалізуватися на сторінці.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return mockedComponent.find('AppWrapper').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
Компонент `Provider` повинен мати prop з `store` переданий до нього, що рівний сховищу 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