#javascript #reactjs #styled-components
#javascript #reactjs #styled-компоненты
Вопрос:
Я учусь использовать react memo, я применяю его к простому приложению задач, моя проблема в том, что я не знаю, как заставить стили элементов в списке работать хорошо.
Как вы можете видеть при выполнении задачи, я не знаю, как обновить стили других элементов в списке, чтобы один был белым, а другой черным.
Я пробовал много вещей, но ничего не получалось : (
TaskItem.jsx
import React, { memo } from 'react'
import styled from "styled-components"
import { useSelector } from "react-redux";
import store from "../../redux/store";
//STYLES
const DIV = styled.div`
max-height: ${
props => !useSelector((state)=> state.toggleDoneTasks.show) amp;amp; props.done ? "0px" : "50px"
};
opacity: ${
props => !useSelector((state)=> state.toggleDoneTasks.show) amp;amp; props.done ? "0": "1"
};
padding: ${
props => !useSelector((state)=> state.toggleDoneTasks.show) amp;amp; props.done ? "0px":"12px 15px"
};
overflow: hidden;
transition: opacity 0.5s, max-height 0.5s, padding 0.5s;
`;
const TR = styled.tr`
background-color: ${
(props) => {
//show completed and not completed tasks
if(useSelector((state)=> state.toggleDoneTasks.show)){
return props.index % 2 === 0 ? '#f3f3f3': 'none'
}
const tasksNotDone = props.tasks.filter((task) => !task.done)
const index = tasksNotDone.findIndex(t => t.id === props.task.id)
return index % 2 === 0 ? '#f3f3f3': 'none'
}
};
/*
amp;:nth-child(even) {background: #CCC};
amp;:nth-child(odd) {background: #FFF};
*/
border-bottom: ${
props => !useSelector((state)=> state.toggleDoneTasks.show) amp;amp; props.task.done ? "none": "1px solid #dddddd"
};;
transition: visibility 0.5s;
cursor: pointer;
amp;:hover{
font-weight: bold;
color: #009879;
}
`;
function TaskRow({ task, toggleDoneTask, index, tasks }) {
return (
<TR task={task} tasks={tasks} index={index}>
<td>
<DIV done={task.done}>
{console.log('render', task)}
{task.title}
</DIV>
</td>
<td>
<DIV done={task.done}>
{task.description}
</DIV>
</td>
<td>
<DIV done={task.done}>
<input type="checkbox"
checked={task.done}
onChange={toggleDoneTask}
style={{cursor: 'pointer'}}
/>
</DIV>
</td>
</TR>
)
}
export default memo(TaskRow, (prev, next) => {
// store.getState().toggleDoneTasks.show
//COMPARE TASK OBJECT
const prevTaskKeys = Object.keys(prev.task);
const nextTaskKeys = Object.keys(next.task);
const sameLength = prevTaskKeys.length === nextTaskKeys.length;
const sameEntries = prevTaskKeys.every(key => {
return nextTaskKeys.includes(key) amp;amp; prev.task[key] === next.task[key];
});
return sameLength amp;amp; sameEntries;
})
Задачи.jsx
import React, { useEffect, useReducer } from "react";
import TaskItem from "./TaskItem";
function saveLocalStorage(tasks) {
localStorage.setItem("tasks", JSON.stringify(tasks));
}
function TasksReducer(taskItems, { type, task }) {
switch (type) {
case "UPDATE_TAKS": {
let taskItemsCopy = [...taskItems].map((task) => ({ ...task }));
let newItems = taskItemsCopy.map((t) => {
if (t.id === task.id) {
t.done = !t.done;
}
return t;
});
saveLocalStorage(newItems);
return newItems;
}
case "ADD_TASK": {
const newItems = [...taskItems, task];
saveLocalStorage(newItems);
return newItems;
}
default:
window.alert("INVALID ACTION");
break;
}
}
const initialState = JSON.parse(localStorage.getItem("tasks")) || [];
//STYLES
const styleTable = {
borderCollapse: "collapse",
margin: "25px 0",
fontSize: "0.9em",
fontFamily: "sans-serif",
minWidth: "400px",
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)"
};
const styleTr = {
backgroundColor: "#009879",
color: "#ffffff",
textAlign: "left"
};
const styleTh = {
padding: "12px 15px"
};
function Tasks({ newTask, show }) {
const [taskItems, dispatch] = useReducer(TasksReducer, initialState);
useEffect(() => {
if (!newTask) return;
newTaskHandler({ id: taskItems.length 1, ...newTask });
}, [newTask]);
const newTaskHandler = (task) => {
dispatch({ type: "ADD_TASK", task });
};
const toggleDoneTask = (task) => {
dispatch({ type: "UPDATE_TAKS", task });
};
return (
<React.Fragment>
<h1>learning react </h1>
<table style={styleTable}>
<thead>
<tr style={styleTr}>
<th style={styleTh}>Title</th>
<th style={styleTh}>Description</th>
<th style={styleTh}>Done</th>
</tr>
</thead>
<tbody>
{taskItems.map(
(task, i) =>
(show || !task.done) amp;amp; (
<TaskItem
tasks={taskItems}
index={i}
task={task}
key={task.id}
show={show}
toggleDoneTask={() => toggleDoneTask(task)}
/>
)
)}
</tbody>
</table>
</React.Fragment>
);
}
export default Tasks;
Ответ №1:
О Memo и вашем приложении
Memo
может быть полезно, когда вы не ожидаете, что ваш компонент будет часто меняться. Это связано со стоимостью, которая выполняет эти оценки, чтобы проверить, следует ли выполнять повторный запуск.
В вашем случае у вас было бы много повторных переходов, потому что несколько компонентов должны были бы быть повторными, чтобы соответствовать правильному фону, что является недостатком использования Memo
.
Кроме того, вы увидите эти ошибки, поскольку другие компоненты не будут повторно отображаться, поскольку их реквизиты не изменятся.
Я бы посоветовал удалить memo для этого случая.
О вашем состоянии отображения
Вы видите эффект аккордеона, который у вас есть, верно? Вы бы не увидели этого эффекта, если бы компонент был размонтирован. Это означает, что компонент никогда не отключается. Вы используете 2 show
состояния: одно состояние, созданное в корневом каталоге вашего приложения, и другое состояние, полученное из вашего редуктора.
Ваша кнопка только меняет состояние редуктора show
. Но для рендеринга TaskItem
вы используете созданное устаревшее состояние отображения, это всегда верно. Если бы вы использовали состояние redux, никакого эффекта вообще не было бы:
// show is always true unless you pass state from your reducer like:
// const show = useSelector((state) => state.toggleDoneTasks.show);
(show || !task.done) amp;amp; (
<TaskItem
tasks={taskItems}
index={i}
task={task}
key={task.id}
show={show}
toggleDoneTask={() => toggleDoneTask(task)}
/>
)
поэтому вам следует удалить состояние React show:
const [show, setShow] = useState(JSON.parse(localStorage.getItem('show')) || true)
Или удалите свой редуктор, но для целей обучения вы можете сохранить редуктор.
Песочница без дополнительного состояния и без эффектов:
Об эффекте аккордеона
Учитывая, что у вас больше не будет эффекта аккордеона. Для решения этой проблемы вы можете либо:
- выберите библиотеку анимации react, которая обрабатывает эффекты перехода при монтировании и размонтировании компонентов по вашему усмотрению;
- визуализация всех задач (без
(show || !task.done)
условий). И отслеживайте в каждой задаче, сколько задачdone
до этой задачи. С помощью этого вы можете выполнить некоторую логику, например:
const indexToPass = show ? index : index - doneTasksBefore
...
<TR task={task} tasks={tasks} index={indexToPass}>
Комментарии:
1. Спасибо, теперь мне стало понятнее, но у меня последний вопрос: есть ли способ оптимизировать очень большой список? предотвращение повторного отображения списка при изменении элемента
2. Это может стать сложным, и я также хотел бы получить четкий ответ на этот вопрос! Что я могу добавить, так это то, что когда у вас сложные состояния, вы бы с большей вероятностью справились с этим
redux
(точно так же, как вы делали с show), и есть библиотека,reselect
которая позволяет запоминать селекторы, которые, я думаю, вам понравятся, ура! npmjs.com/package/reselect