Создайте приложение списка дел в React с помощью хуков
Научитесь создавать приложения React, используя функциональные компоненты и управление состоянием.
React — одна из самых популярных и простых библиотек JavaScript для создания пользовательских интерфейсов (UI), поскольку она позволяет создавать повторно используемые компоненты пользовательского интерфейса.
Компоненты в React — это независимые, многократно используемые фрагменты кода, которые служат строительными блоками приложения. Функциональные компоненты React — это функции JavaScript, которые отделяют уровень представления от бизнес-логики. Согласно документации React, простой функциональный компонент можно написать так:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
Функциональные компоненты React не имеют состояния. Компоненты без состояния объявляются как функции, которые не имеют состояния и возвращают одну и ту же разметку с одинаковыми реквизитами. Состояние управляется в компонентах с помощью хуков, которые были представлены в React 16.8. Они позволяют управлять состоянием и жизненным циклом функциональных компонентов. Существует несколько встроенных хуков, но вы также можете создавать собственные хуки.
В этой статье объясняется, как создать простое приложение для дел в React, используя функциональные компоненты и управление состоянием. Полный код этого приложения доступен на GitHub и CodeSandbox. Когда вы закончите изучение этого руководства, приложение будет выглядеть так:
(Джайвардхан Кумар, CC BY-SA 4.0)
Предварительные условия
- Для локальной сборки у вас должен быть Node.js 10.16 или новее, Yarn v1.20.0 или новее и npm 5.6.
- Базовые знания JavaScript
- Базовое понимание React будет плюсом
Создайте приложение React
Create React App — это среда, которая позволяет вам начать создавать приложение React. Наряду с этим руководством я использовал шаблон TypeScript для добавления определений статических типов. TypeScript — это язык с открытым исходным кодом, основанный на JavaScript:
npx create-react-app todo-app-context-api --template typescript
npx — инструмент запуска пакетов; как вариант, можно использовать пряжу:
yarn create react-app todo-app-context-api --template typescript
После выполнения этой команды вы можете перейти в каталог и запустить приложение:
cd todo-app-context-api
yarn start
Вы должны увидеть стартовое приложение и логотип React, созданный шаблонным кодом. Поскольку вы создаете собственное приложение React, вы сможете изменить логотип и стили в соответствии со своими потребностями.
Создайте приложение для дел
Приложение дел может:
- Добавить элемент
- Список элементов
- Отметить элементы как выполненные
- Удалить элементы
- Фильтрация элементов по статусу (например, завершено, все, активно)
(Джайвардхан Кумар, CC BY-SA 4.0)
Компонент заголовка
Создайте каталог с именем компоненты и добавьте файл с именем Header.tsx:
mkdir components
cd components
vi Header.tsx
Заголовок — это функциональный компонент, который содержит заголовок:
const Header: React.FC = () => {
return (
<div className="header">
<h1>
Add TODO List!!
</h1>
</div>
)
}
Компонент AddTodo
Компонент AddTodo содержит текстовое поле и кнопку. Нажатие кнопки добавляет элемент в список.
Создайте каталог с именем todo в каталоге comments и добавьте файл с именем AddTodo.tsx:
mkdir todo
cd todo
vi AddTodo.tsx
AddTodo — функциональный компонент, принимающий реквизиты. Пропсы допускают одностороннюю передачу данных, т. е. только от родительского компонента к дочернему:
const AddTodo: React.FC<AddTodoProps> = ({ todoItem, updateTodoItem, addTaskToList }) => {
const submitHandler = (event: SyntheticEvent) => {
event.preventDefault();
addTaskToList();
}
return (
<form className="addTodoContainer" onSubmit={submitHandler}>
<div className="controlContainer">
<input className="controlSpacing" style={{flex: 1}} type="text" value={todoItem?.text ?? ''} onChange={(ev) => updateTodoItem(ev.target.value)} placeholder="Enter task todo ..." />
<input className="controlSpacing" style={{flex: 1}} type="submit" value="submit" />
</div>
<div>
<label>
<span style={{ color: '#ccc', padding: '20px' }}>{todoItem?.text}</span>
</label>
</div>
</form>
)
}
Вы создали функциональный компонент React под названием AddTodo, который принимает реквизиты, предоставленные родительской функцией. Это делает компонент многоразовым. Реквизиты, которые необходимо передать:
- todoItem: состояние пустого элемента.
- updateToDoItem: вспомогательная функция для отправки обратных вызовов родительскому элементу по мере ввода пользователем.
- addTaskToList: функция для добавления элемента в список дел.
Есть также некоторые стили и элементы HTML, такие как форма, ввод и т. д.
Компонент TodoList
Следующий компонент, который нужно создать, — это TodoList. Он отвечает за перечисление элементов в состоянии дел и предоставление возможностей для удаления и пометки элементов как выполненных.
TodoList будет функциональным компонентом:
const TodoList: React.FC = ({ listData, removeItem, toggleItemStatus }) => {
return listData.length > 0 ? (
<div className="todoListContainer">
{ listData.map((lData) => {
return (
<ul key={lData.id}>
<li>
<div className="listItemContainer">
<input type="checkbox" style={{ padding: '10px', margin: '5px' }} onChange={() => toggleItemStatus(lData.id)} checked={lData.completed}/>
<span className="listItems" style={{ textDecoration: lData.completed ? 'line-through' : 'none', flex: 2 }}>{lData.text}</span>
<button type="button" className="listItems" onClick={() => removeItem(lData.id)}>Delete</button>
</div>
</li>
</ul>
)
})}
</div>
) : (<span> No Todo list exist </span >)
}
TodoList также является функциональным компонентом React многократного использования, который принимает реквизиты из родительских функций. Реквизиты, которые необходимо передать:
- listData: список задач с идентификаторами, текстом и завершенными свойствами.
- removeItem: вспомогательная функция для удаления элемента из списка дел.
- toggleItemStatus: функция переключения статуса задачи с выполненного на невыполненное и наоборот.
Также есть некоторые стили и элементы HTML (например, списки, ввод и т. д.).
Компонент нижнего колонтитула
Нижний колонтитул будет функциональным компонентом; создайте его в каталоге comments следующим образом:
cd ..
const Footer: React.FC = ({item = 0, storage, filterTodoList}) => {
return (
<div className="footer">
<button type="button" style={{flex:1}} onClick={() => filterTodoList(ALL_FILTER)}>All Item</button>
<button type="button" style={{flex:1}} onClick={() => filterTodoList(ACTIVE_FILTER)}>Active</button>
<button type="button" style={{flex:1}} onClick={() => filterTodoList(COMPLETED_FILTER)}>Completed</button>
<span style={{color: '#cecece', flex:4, textAlign: 'center'}}>{item} Items | Make use of {storage} to store data</span>
</div>
);
}
Он принимает три реквизита:
- item: отображает количество элементов.
- хранилище: отображает текст.
- filterTodoList: функция для фильтрации задач по статусу (активно, завершено, все элементы).
Компонент Todo: управление состоянием с помощью contextApi и useReducer
(Джайвардхан Кумар, CC BY-SA 4.0)
Контекст предоставляет способ передавать данные через дерево компонентов без необходимости передавать реквизиты вручную на каждом уровне. ContextApi и useReducer можно использовать для управления состоянием, разделяя его по всему дереву компонентов React, не передавая его в качестве реквизита каждому компоненту в дереве.
Теперь, когда у вас есть компоненты AddTodo, TodoList и Footer, вам нужно их связать.
Используйте следующие встроенные перехватчики для управления состоянием и жизненным циклом компонентов:
- useState: возвращает значение с отслеживанием состояния и функцию обновления для обновления состояния.
- useEffect: помогает управлять жизненным циклом функциональных компонентов и выполнять побочные эффекты.
- useContext принимает объект контекста и возвращает текущее значение контекста.
- useReducer: Как и useState, он возвращает значение с отслеживанием состояния и функцию обновления, но он используется вместо useState, когда у вас сложная логика состояния (например, несколько подзначений или если новое состояние зависит от предыдущего). один)
Сначала используйте перехватчики contextApi и useReducer для управления состоянием. Для разделения задач добавьте новый каталог в компоненты под названием contextApiComponents:
mkdir contextApiComponents
cd contextApiComponents
Создайте TodoContextApi.tsx:
const defaultTodoItem: TodoItemProp = { id: Date.now(), text: '', completed: false };
const TodoContextApi: React.FC = () => {
const { state: { todoList }, dispatch } = React.useContext(TodoContext);
const [todoItem, setTodoItem] = React.useState(defaultTodoItem);
const [todoListData, setTodoListData] = React.useState(todoList);
React.useEffect(() => {
setTodoListData(todoList);
}, [todoList])
const updateTodoItem = (text: string) => {
setTodoItem({
id: Date.now(),
text,
completed: false
})
}
const addTaskToList = () => {
dispatch({
type: ADD_TODO_ACTION,
payload: todoItem
});
setTodoItem(defaultTodoItem);
}
const removeItem = (id: number) => {
dispatch({
type: REMOVE_TODO_ACTION,
payload: { id }
})
}
const toggleItemStatus = (id: number) => {
dispatch({
type: UPDATE_TODO_ACTION,
payload: { id }
})
}
const filterTodoList = (type: string) => {
const filteredList = FilterReducer(todoList, {type});
setTodoListData(filteredList)
}
return (
<>
<AddTodo todoItem={todoItem} updateTodoItem={updateTodoItem} addTaskToList={addTaskToList} />
<TodoList listData={todoListData} removeItem={removeItem} toggleItemStatus={toggleItemStatus} />
<Footer item={todoListData.length} storage="Context API" filterTodoList={filterTodoList} />
</>
)
}
Этот компонент включает компоненты AddTodo, TodoList и Footer, а также соответствующие им вспомогательные функции и функции обратного вызова.
Для управления состоянием он использует contextApi, который предоставляет методы состояния и отправки, которые, в свою очередь, обновляют состояние. Он принимает объект контекста. (Далее вы создадите поставщика контекста под названием contextProvider).
const { state: { todoList }, dispatch } = React.useContext(TodoContext);
ТодоПровайдер
Добавьте TodoProvider, который создает контекст и использует перехватчик useReducer. Хук useReducer принимает функцию редуктора вместе с начальными значениями и возвращает функции состояния и обновления (отправка).
Создайте контекст и экспортируйте его. Экспорт позволит использовать его любому дочернему компоненту для получения текущего состояния с помощью перехватчика useContext:
export const TodoContext = React.createContext({} as TodoContextProps);
Создайте ContextProvider и экспортируйте его:
const TodoProvider : React.FC = (props) => { const [state, dispatch] = React.useReducer(TodoReducer, {todoList: []}); const value = {state, dispatch} return ( <TodoContext.Provider value={value}> {props.children} </TodoContext.Provider> ) }
Доступ к данным контекста может быть доступен любому компоненту React в иерархии напрямую с помощью хука useContext, если вы обертываете родительский компонент (например, TodoContextApi) или само приложение с помощью поставщика ( например, TodoProvider):
<TodoProvider> <TodoContextApi /> </TodoProvider>
В компоненте TodoContextApi используйте хук useContext для доступа к текущему значению контекста:
const { state: { todoList }, dispatch } = React.useContext(TodoContext)
TodoProvider.tsx:
type TodoContextProps = {
state : {todoList: TodoItemProp[]};
dispatch: ({type, payload}: {type:string, payload: any}) => void;
}
export const TodoContext = React.createContext({} as TodoContextProps);
const TodoProvider : React.FC = (props) => {
const [state, dispatch] = React.useReducer(TodoReducer, {todoList: []});
const value = {state, dispatch}
return (
<TodoContext.Provider value={value}>
{props.children}
</TodoContext.Provider>
)
}
Редукторы
Редюсер — это чистая функция без побочных эффектов. Это означает, что для одних и тех же входных данных ожидаемый результат всегда будет одинаковым. Это упрощает изолированное тестирование редьюсера и помогает управлять состоянием. TodoReducer и FilterReducer используются в компонентах TodoProvider и TodoContextApi.
Создайте каталог с именем reducers в src и создайте там файл с именем TodoReducer.tsx:
const TodoReducer = (state: StateProps = {todoList:[]}, action: ActionProps) => {
switch(action.type) {
case ADD_TODO_ACTION:
return { todoList: [...state.todoList, action.payload]}
case REMOVE_TODO_ACTION:
return { todoList: state.todoList.length ? state.todoList.filter((d) => d.id !== action.payload.id) : []};
case UPDATE_TODO_ACTION:
return { todoList: state.todoList.length ? state.todoList.map((d) => {
if(d.id === action.payload.id) d.completed = !d.completed;
return d;
}): []}
default:
return state;
}
}
Создайте FilterReducer для сохранения состояния фильтра:
const FilterReducer =(state : TodoItemProp[] = [], action: ActionProps) => {
switch(action.type) {
case ALL_FILTER:
return state;
case ACTIVE_FILTER:
return state.filter((d) => !d.completed);
case COMPLETED_FILTER:
return state.filter((d) => d.completed);
default:
return state;
}
}
Вы создали все необходимые компоненты. Затем вы добавите компоненты Header и TodoContextApi в App, а также TodoContextApi с TodoProvider, чтобы все дочерние элементы могли получить доступ к контексту.
function App() {
return (
<div className="App">
<Header />
<TodoProvider>
<TodoContextApi />
</TodoProvider>
</div>
);
}
Убедитесь, что компонент приложения находится в index.tsx внутри ReactDom.render. ReactDom.render принимает два аргумента: элемент React и идентификатор элемента HTML. Элемент React отображается на веб-странице, а id указывает, какой элемент HTML будет заменен элементом React:
ReactDOM.render(
<App />,
document.getElementById('root')
);
Заключение
Вы узнали, как создать функциональное приложение в React, используя перехватчики и управление состоянием. Что ты будешь с этим делать?