React и Redux вместе. Часть 1 из 7
22.08.2022
Теги: Frontend • Hook • JavaScript • React.js • Web-разработка • Состояние • Список • Теория • Функция
Механизм локального хранилища компонента, который поставляется вместе с React неудобен тем, что такое хранилище изолировано. Если разные независимые компоненты должны реагировать на событие — придётся либо передавать локальное состояние в виде пропсов дочерним компонентам, либо переносить локальное состояние вверх до ближайшего общего предка. Использование Redux позволяет решить эту проблему — все компоненты могут получить доступ к глобальному хранилищу состояния приложения.
Простое React приложение без Redux
Создадим приложение списка задач и посмотрим, с какими трудностями приходится сталкиваться при разработке без использования Redux. Это будет список задач, которые можно добавлять, удалять и изменять статус.
$ cd react-redux/react-without-redux # директория проекта $ npx create-react-app . # развертываем react-приложение
Первый вариант
Убираем все лишнее, редактируем компонент App.js
, создаем директорию src/component
, внутри нее — компоненты TodoList.js
, TodoItem.js
, TodoForm.js
и StatusBar.js
.
Файл компонента App.js
:
import './App.css'; import { useState } from 'react'; import { TodoList } from './component/TodoList.js'; import { TodoForm } from './component/TodoForm.js'; import { StatusBar } from './component/StatusBar.js'; import { v4 as uuid } from 'uuid'; const initState = [ { id: 1, title: 'Первая задача', completed: false }, { id: 2, title: 'Вторая задача', completed: true }, { id: 3, title: 'Третья задача', completed: false }, { id: 4, title: 'Четвертая задача', completed: true }, { id: 5, title: 'Пятая задача', completed: false }, ]; function App() { const [todos, setTodos] = useState(initState); const create = text => { const newTodo = { id: uuid(), title: text, completed: false, }; setTodos([...todos, newTodo]); }; const toggle = id => { const newTodos = todos.map(todo => { return todo.id === id ? { ...todo, completed: !todo.completed } : todo; }); setTodos(newTodos); }; const remove = id => { setTodos(todos.filter(todo => todo.id !== id)); }; const completed = () => todos.filter(todo => todo.completed).length; const uncompleted = () => todos.filter(todo => !todo.completed).length; return ( <div className="App"> <h1>Список задач</h1> <TodoForm create={create} /> <StatusBar total={todos.length} completed={completed()} uncompleted={uncompleted()} /> <TodoList todos={todos} toggle={toggle} remove={remove} /> </div> ); } export default App;
Файл компонента TodoList.js
:
import { TodoItem } from './TodoItem.js'; export function TodoList(props) { const { todos, toggle, remove } = props; return ( <div className="todo-list"> {todos.length > 0 ? ( todos.map(todo => ( <TodoItem key={todo.id} {...todo} toggle={toggle} remove={remove} /> )) ) : ( <p>Список задач пустой</p> )} </div> ); }
Файл компонента TodoItem.js
:
export function TodoItem(props) { const { id, title, completed, toggle, remove } = props; return ( <div className="todo-item"> <span> <input type="checkbox" checked={completed} onChange={() => toggle(id)} /> {title} </span> <span className="remove" onClick={() => remove(id)}> × </span> </div> ); }
Файл компонента TodoForm.js
:
import { useState } from 'react'; export function TodoForm(props) { const [text, setText] = useState(''); const handleChange = event => { setText(event.target.value); }; const handleClick = () => { if (text.trim().length !== 0) { props.create(text); } setText(''); }; return ( <div className="todo-form"> <input type="text" value={text} onChange={handleChange} placeholder="Новая задача" /> <button onClick={handleClick}>Добавить</button> </div> ); }
Файл компонента StatusBar.js
:
export function StatusBar(props) { const { total, completed, uncompleted } = props; return ( <div className="status-bar"> Всего задач {total}, не завершенных {uncompleted}, завершенных {completed}. </div> ); }
Мы могли бы хранить состояние списка задач todos
внутри компонента TodoList.js
, но пришлось переносить его наверх, в компонент App.js
, потому что это состояние необходимо для работы компонента StatusBar.js
. И после переноса состояния наверх, нам теперь нужно передавать функции toggle
и remove
через пропсы вниз — от App.js
к TodoList.js
, от TodoList.js
к TodoItem.js
. Совсем маленькое приложение — а уже столько хлопот из-за отсутствия централизованного хранилища без доступа к нему из любого места.
Исходные коды здесь, директория without-redux-one
.
$ cd react-redux/without-redux-one $ npm install
Второй вариант
Убираем все лишнее, редактируем компонент App.js
, создаем директорию src/component
, внутри нее — компоненты TodoList.js
, TodoItem.js
, TodoForm.js
и StatusBar.js
. Кроме того, создаем компонент TodoContext.js
, который будет хранить состояние списка задач и предоставлять к нему доступ всем компонентам приложения через хук useContext
.
Файл компонента TodoContext.js
:
import { createContext, useState } from 'react'; import { v4 as uuid } from 'uuid'; export const TodoContext = createContext(); const initState = [ { id: 1, title: 'Первая задача', completed: false }, { id: 2, title: 'Вторая задача', completed: true }, { id: 3, title: 'Третья задача', completed: false }, { id: 4, title: 'Четвертая задача', completed: true }, { id: 5, title: 'Пятая задача', completed: false }, ]; export function TodoContextProvider(props) { const [todos, setTodos] = useState(initState); const create = text => { const newTodo = { id: uuid(), title: text, completed: false, }; setTodos([...todos, newTodo]); }; const toggle = id => { const newTodos = todos.map(todo => { return todo.id === id ? { ...todo, completed: !todo.completed } : todo; }); setTodos(newTodos); }; const remove = id => { setTodos(todos.filter(todo => todo.id !== id)); }; const completed = () => todos.filter(todo => todo.completed).length; const uncompleted = () => todos.filter(todo => !todo.completed).length; const context = { todos: todos, create: create, toggle: toggle, remove: remove, completed: completed, uncompleted: uncompleted, }; return ( <TodoContext.Provider value={context}> {props.children} </TodoContext.Provider> ); }
Файл компонента App.js
:
import './App.css'; import { TodoContextProvider } from './component/TodoContext.js'; import { TodoList } from './component/TodoList.js'; import { TodoForm } from './component/TodoForm.js'; import { StatusBar } from './component/StatusBar.js'; function App() { return ( <TodoContextProvider> <div className="App"> <h1>Список задач</h1> <TodoForm /> <StatusBar /> <TodoList /> </div> </TodoContextProvider> ); } export default App;
Файл компонента TodoList.js
:
import { useContext } from 'react'; import { TodoContext } from './TodoContext.js'; import { TodoItem } from './TodoItem.js'; export function TodoList(props) { const context = useContext(TodoContext); return ( <div className="todo-list"> {context.todos.length > 0 ? ( context.todos.map(todo => <TodoItem key={todo.id} {...todo} />) ) : ( <p>Список задач пустой</p> )} </div> ); }
Файл компонента TodoItem.js
:
import { useContext } from 'react'; import { TodoContext } from './TodoContext.js'; export function TodoItem(props) { const { id, title, completed } = props; const { toggle, remove } = useContext(TodoContext); return ( <div className="todo-item"> <span> <input type="checkbox" checked={completed} onChange={() => toggle(id)} /> {title} </span> <span className="remove" onClick={() => remove(id)}> × </span> </div> ); }
Файл компонента TodoForm.js
:
import { useContext, useState } from 'react'; import { TodoContext } from './TodoContext.js'; export function TodoForm(props) { const [text, setText] = useState(''); const context = useContext(TodoContext); const handleChange = event => { setText(event.target.value); }; const handleClick = () => { if (text.trim().length !== 0) { context.create(text); } setText(''); }; return ( <div className="todo-form"> <input type="text" value={text} onChange={handleChange} placeholder="Новая задача" /> <button onClick={handleClick}>Добавить</button> </div> ); }
Файл компонента StatusBar.js
:
import { useContext } from 'react'; import { TodoContext } from './TodoContext.js'; export function StatusBar(props) { const { todos, completed, uncompleted } = useContext(TodoContext); return ( <div className="status-bar"> Всего задач {todos.length}, не завершенных {uncompleted()}, завершенных {completed()}. </div> ); }
Мы обернули все приложение в <TodoContextProvider>
— и теперь все потомки <App>
могут получить доступ к состоянию списка задач и к методам для его изменения (см. здесь и здесь).
Исходные коды здесь, директория without-redux-two
.
$ cd react-redux/without-redux-two $ npm install
Поиск: Frontend • Hook • JavaScript • React.js • Web-разработка • Состояние • Список • Теория • Функция