javascript - 使用 react 备忘录动态改变样式

标签 javascript reactjs styled-components

我正在学习使用react memo,我将其应用到一个简单的任务应用程序中,我的问题是我不知道如何使列表中项目的样式很好地工作。

Image from Gyazo

正如您在完成任务时所看到的,我不知道如何更新列表中其他项目的样式,使其变为白色和黑色。

我尝试了很多方法,但没有任何效果:(

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) && props.done ? "0px" : "50px"
    };

    opacity: ${
        props => !useSelector((state)=> state.toggleDoneTasks.show) && props.done ? "0": "1"
    };

    padding: ${
        props => !useSelector((state)=> state.toggleDoneTasks.show) && 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'

        }
    };

    /* 
    &:nth-child(even) {background: #CCC};
    &:nth-child(odd) {background: #FFF}; 
    */

    border-bottom: ${
        props => !useSelector((state)=> state.toggleDoneTasks.show) && props.task.done ? "none": "1px solid #dddddd"
    };;

    transition: visibility 0.5s;

    cursor: pointer;
    &: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) && prev.task[key] === next.task[key];
    });

    return sameLength && 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) && (
                <TaskItem
                  tasks={taskItems}
                  index={i}
                  task={task}
                  key={task.id}
                  show={show}
                  toggleDoneTask={() => toggleDoneTask(task)}
                />
              )
          )}
        </tbody>
      </table>
    </React.Fragment>
  );
}

export default Tasks;

如果您需要完整的代码: Edit inspiring-sea-hp9kt

最佳答案

关于 Memo 和您的应用

当您不希望组件经常更改时,

Memo 可能会很有用。它需要进行这些评估以验证是否应该重新渲染的成本。

在您的情况下,您将需要进行大量重新渲染,因为需要重新渲染多个组件以适应正确的背景,这是使用 Memo 的缺点。

此外,您还会看到这些错误,因为其他组件不会重新渲染,因为它们的 props 不会改变。

我建议删除此案例的备忘录。

关于您的演出状态

你看到 Accordion 效果了吗?如果组件确实卸载了,您将看不到这种效果。这意味着该组件永远不会卸载。您正在使用 2 个 show 状态,一个状态在您的应用程序的根目录下创建,另一个状态来自您的 reducer 。

您的按钮仅翻转 reducer 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) && (
                <TaskItem
                  tasks={taskItems}
                  index={i}
                  task={task}
                  key={task.id}
                  show={show}
                  toggleDoneTask={() => toggleDoneTask(task)}
                />
              )

所以你应该删除你的 React 显示状态:

const [show, setShow] = useState(JSON.parse(localStorage.getItem('show')) || true)

或者删除您的 reducer ,但出于学习目的,您可以保留 reducer 。

没有额外状态的沙箱,没有效果: sandbox

关于 Accordion 效果

鉴于您将不再有 Accordion 效果。为了解决这个问题,您可以:

  • 选择一个 React 动画库,可以根据您的喜好处理组件安装和卸载的过渡效果;

  • 渲染所有任务(不带 (show || !task.done) 条件)。并跟踪每个任务在该给定任务之前完成的任务数量。这样你就可以做一些类似的逻辑:

    const indexToPass = show ? index : index - doneTasksBefore 

   ...

  <TR task={task} tasks={tasks} index={indexToPass}>

关于javascript - 使用 react 备忘录动态改变样式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66039067/

相关文章:

css - Styled-components 多个伪元素

javascript - Bitmaprenderer 应该在网络 worker 中的 OffscreenCanvas 上工作吗?

javascript - 页面加载 jQuery 表单提交不起作用

javascript - 使用 for 循环和 document.getElementById 在 Javascript 中填充数组

reactjs - 我尝试将 Material-UI RaisingButton 链接到外部 url 但没有成功?

javascript - 如何防止在 React 中使用钩子(Hook)重新呈现页面?

javascript - 为什么事件指示器不隐藏在 native 脚本中?

javascript - React/MUI Popover 使用 anchorPosition 定位不正确

javascript - 使用 Gatsby Js 在单击 anchor 时 react 切换事件类

javascript - 为什么我可以在 styled-components 上引用一个组件而其他人不能?