React и Redux вместе. Часть 1 из 7

22.08.2022

Теги: FrontendHookJavaScriptReact.jsWeb-разработкаСостояниеСписокТеорияФункция

Механизм локального хранилища компонента, который поставляется вместе с 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)} />
                &nbsp;
                {title}
            </span>
            <span className="remove" onClick={() => remove(id)}>
                &times;
            </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)} />
                &nbsp;
                {title}
            </span>
            <span className="remove" onClick={() => remove(id)}>
                &times;
            </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-разработка • Состояние • Список • Теория • Функция

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.