高性能渲染10000条todo list

98 阅读2分钟

todolist相信大家在学js的时候都做过,今天结合一下性能优化来处理todolist

编写todolist

主要功能: 1.todo list 由若干项组成,每一项包含一个勾选框,文本,编辑按钮和删除按钮。点击编辑按钮可以编辑该项的文本,点击删除可以删除该项 2. 顶部有新增,删除(删除所有勾选项),全选三个按钮

整体效果如下

image.png

编辑:

image.png

要实现上面的功能第一步就是对组件进行拆分:当然对于这个demo你也可以选择不拆,直接在一个组件中写,这样就不存在数据的共享了,我这里是拆成了四个组件,不同的功能对应一个组件,如下图所示,红色的是整个外层容器,绿色的主要是一个input输入框,用来添加todolist项以及删除,全选。黄色的是整个列表,紫色的每一个的项。

image.png

组件拆分完后就要进行数据的共享问题了,因为存在兄弟组件,所以组件的通信要么用redux等其他状态库,要么就只有使用props将数据存在他们公共的父组件了,也就只有最外层的红色组件中了,

**项目结构:

App中

image.png

Content中

image.png

性能优化

下一步就是需要对10000条数据的性能优化了,这里我使用的是虚拟列表的方式,也就是只展示可视区域中的数据的dom,这样浏览器的解析速度就会更快一点

image.png

布局:

image.png

关于虚拟列表的实现网上已经有很多好的文章,我这里就不讲了。最后附上代码:

App:

import "./App.css";
import React, { PureComponent } from "react";
import Header from "./Header";
import Content from "./Content";
class App extends PureComponent {
  constructor() {
    super();
    this.state = {

      data:new Array(10000).fill(1).map((item,index)=>{
        return {id:index,value:`标题${index}`,checked:false}
      })
    };
  }
  //添加
  addData = (value) => {
    const { data } = this.state;
    const newData = {
      value,
      id: Math.random().toString().split(5),
      checked: false,
    };
    this.setState({
      data: [...data, newData],
    });
  };
  //删除
  deleteItem = (id) => {
    console.log(id);
    const { data } = this.state;
    const deleteList = data.filter((item) => item.id !== id);
    this.setState({
      data: deleteList,
    });
  };
  //编辑
  edit = (e) => {
    const { data } = this.state;
    this.setState((prevState) => ({
      data: prevState.data.map((item) => {
        if (item.id === e.id) {
          return { ...item, value: e.value };
        }
        return item;
      }),
    }));
  };
  //多选
  changeCheck = (e) => {
    console.log(e);
    this.setState((prevState) => ({
      data: prevState.data.map((item) => {
        if (item.id === e.id) {
          return { ...item, checked: e.checked };
        }
        return item;
      }),
    }));
  };
  //全选
  allChecked = () => {
    this.setState((prevState) => ({
      data: prevState.data.map((item) => {
        return { ...item, checked: true };
      }),
    }));
  };
  //批量删除
  deleteCheckedItem = () => {
    const { data } = this.state;
    const deleteList = data.filter((item) => item.checked !== true);
    this.setState({
      data: deleteList,
    });
  };
  render() {
    const { data } = this.state;
    return (
      <div>
        <Header
          addData={this.addData}
          allChecked={this.allChecked}
          deleteCheckedItem={this.deleteCheckedItem}
        />
        <Content
          data={data}
          deleteItem={this.deleteItem}
          edit={this.edit}
          changeCheck={this.changeCheck}
        />
      </div>
    );
  }
}
export default App;

Header

import React, { PureComponent } from "react";

export default class Header extends PureComponent {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  addItem = () => {
    const { addData } = this.props;
    const { value } = this.inputRef.current;
    addData(value);
    this.inputRef.current.value = "";
  };
  render() {
    const {  allChecked, deleteCheckedItem } = this.props;
    return (
      <div>
        <input type="text" ref={this.inputRef} />
        <button onClick={this.addItem}>新增</button>
        <button onClick={deleteCheckedItem}>删除</button>
        <button onClick={allChecked}>全选</button>
      </div>
    );
  }
}

Content:

import React, { PureComponent } from "react";
import Item from "./Item";

export default class Content extends PureComponent {
  constructor() {
    super();
    this.state = {
      start: 0, //开始位置
      end: 0, //结束位置
      offset: 0, //偏移量
    };
    this.screenHeight = 180;
  }
  componentDidMount() {
    this.setState({
      end: Math.ceil(this.screenHeight / 30), //初次渲染dom的个数
    });
  }
  scroll = (e) => {
    //位置
    console.log(e.target.scrollTop);
    let scrollTop = e.target.scrollTop;
    //开始索引
    const start = Math.ceil(scrollTop / 30);
    //结束索引
    const end = start + Math.ceil(this.screenHeight / 30);
    //偏移量
    let offset = scrollTop - (scrollTop % 30);
    console.log(scrollTop, start, end, offset);
    this.setState({
      start,
      end,
      offset,
    });
  };
  render() {
    const { start, end, offset } = this.state;
    console.log("xxx", start, end, offset);
    console.log(this.props);
    const { data, deleteItem, edit, changeCheck } = this.props;
    const listHeight = data.length * 30;
    return (
      <div
        style={{
          position: "relative",
          height: this.screenHeight,
          overflow: "auto",
          width: 300,
          border: "1px solid #ccc",
        }}
        onScroll={this.scroll}
      >
        <div
          style={{
            height: listHeight + "px",
            position: "absolute",
            left: 0,
            top: 0,
            right: 0,
            zIndex: -1,
          }}
        ></div>
        <div
          style={{
            transform: `translate(0,${offset}px)`,
            left: 0,
            right: 0,
            top: 0,
            position: "absolute",
          }}
        >
          {data.slice(start, Math.min(end, data.length)).map((item) => (
            <Item
              offset={offset}
              item={item}
              edit={edit}
              key={item.id}
              deleteItem={deleteItem}
              changeCheck={changeCheck}
            />
          ))}
        </div>
      </div>
    );
  }
}

Item:

import React, { PureComponent } from "react";

export default class Item extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      editState: false,
      //   checked:false
    };
    this.itemInputRef = React.createRef();
  }
  //编辑
  editItem = (e) => {
    console.log(e);
    this.setState({
      editState: true,
    });
  };
  //多选
  changeCheck = (status) => {
    console.log(status.target);
    const { item, changeCheck } = this.props;
    const checkObj = {
      checked: !item.checked,
      id: item.id,
    };
    changeCheck(checkObj);
  };

  //编辑确定
  confimEdit = () => {
    const { edit, item } = this.props;
    console.log(this.props);
    const { value } = this.itemInputRef.current;
    edit({
      id: item.id,
      value,
    });
    this.setState({
      editState: false,
    });
  };
  //删除
  deleteItem = () => {
    const {
      item: { id },
      deleteItem,
    } = this.props;
    console.log(id);
    deleteItem(id);
  };
  render() {
    const { editState } = this.state;
    const { item, changeCheck, offset } = this.props;
    return (
        <div
          style={{
            height: 30,
          }}
        >
          <input
            type="checkbox"
            checked={item.checked}
            onChange={this.changeCheck}
          />
          {editState ? (
            <div style={{ display: "inline-block" }}>
              <input
                type="text"
                defaultValue={item.value}
                ref={this.itemInputRef}
              />
            </div>
          ) : (
            <span>{item.value}</span>
          )}
          {editState ? (
            <button onClick={this.confimEdit}>确定</button>
          ) : (
            <button onClick={this.editItem}>编辑</button>
          )}
          <button onClick={this.deleteItem}>删除</button>
        </div>
    );
  }
}

参考文章:「前端进阶」高性能渲染十万条数据(虚拟列表) - 掘金 (juejin.cn)