MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React 动态增删列表项的方法

2023-08-205.5k 阅读

React 动态增删列表项的方法

在 React 开发中,经常会遇到需要动态增删列表项的场景,比如待办事项列表、购物车商品列表等。实现动态增删列表项不仅能提升用户体验,还能更好地管理数据。下面将详细介绍几种常见的实现方法。

使用数组方法实现动态增删

在 React 中,状态是驱动 UI 更新的关键。通常我们会用数组来存储列表项的数据,通过更新数组来实现列表项的增删。

  1. 添加列表项 假设我们有一个简单的待办事项列表,每项待办事项是一个字符串。首先,在组件的 state 中定义一个数组来存储待办事项:
import React, { Component } from'react';

class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [],
      newTodo: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.addTodo = this.addTodo.bind(this);
  }

  handleChange(event) {
    this.setState({ newTodo: event.target.value });
  }

  addTodo() {
    if (this.state.newTodo.trim()!== '') {
      // 使用数组的 concat 方法添加新的待办事项
      const newTodos = this.state.todos.concat(this.state.newTodo);
      this.setState({
        todos: newTodos,
        newTodo: ''
      });
    }
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.newTodo}
          onChange={this.handleChange}
          placeholder="添加新的待办事项"
        />
        <button onClick={this.addTodo}>添加</button>
        <ul>
          {this.state.todos.map((todo, index) => (
            <li key={index}>{todo}</li>
          ))}
        </ul>
      </div>
    );
  }
}

export default TodoList;

在上述代码中,addTodo 方法通过 concat 方法创建了一个新的数组,将新的待办事项添加到原数组末尾,然后通过 setState 更新 todos 状态,从而触发 UI 的重新渲染。

除了 concat 方法,还可以使用展开运算符 ... 来添加新的列表项:

addTodo() {
  if (this.state.newTodo.trim()!== '') {
    // 使用展开运算符添加新的待办事项
    const newTodos = [...this.state.todos, this.state.newTodo];
    this.setState({
      todos: newTodos,
      newTodo: ''
    });
  }
}

这种方式更加简洁,通过展开原数组,再将新的待办事项添加到新数组中。

  1. 删除列表项 删除列表项同样可以使用数组方法。假设我们要删除某个待办事项,可以根据其索引来操作。
import React, { Component } from'react';

class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [],
      newTodo: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.addTodo = this.addTodo.bind(this);
    this.deleteTodo = this.deleteTodo.bind(this);
  }

  handleChange(event) {
    this.setState({ newTodo: event.target.value });
  }

  addTodo() {
    if (this.state.newTodo.trim()!== '') {
      const newTodos = [...this.state.todos, this.state.newTodo];
      this.setState({
        todos: newTodos,
        newTodo: ''
      });
    }
  }

  deleteTodo(index) {
    // 使用数组的 filter 方法删除指定索引的待办事项
    const newTodos = this.state.todos.filter((_, i) => i!== index);
    this.setState({ todos: newTodos });
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.newTodo}
          onChange={this.handleChange}
          placeholder="添加新的待办事项"
        />
        <button onClick={this.addTodo}>添加</button>
        <ul>
          {this.state.todos.map((todo, index) => (
            <li key={index}>
              {todo}
              <button onClick={() => this.deleteTodo(index)}>删除</button>
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export default TodoList;

deleteTodo 方法中,使用 filter 方法创建了一个新数组,过滤掉了要删除的项。filter 方法会遍历数组,返回一个新数组,新数组中的元素是经过回调函数过滤后符合条件的元素。

使用对象数组实现复杂列表项的增删

在实际应用中,列表项往往是包含多个属性的对象。比如,待办事项可能包含 idtitlecompleted 等属性。

  1. 添加复杂列表项
import React, { Component } from'react';

class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [],
      newTodoTitle: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.addTodo = this.addTodo.bind(this);
  }

  handleChange(event) {
    this.setState({ newTodoTitle: event.target.value });
  }

  addTodo() {
    if (this.state.newTodoTitle.trim()!== '') {
      const newTodo = {
        id: Date.now(),
        title: this.state.newTodoTitle,
        completed: false
      };
      const newTodos = [...this.state.todos, newTodo];
      this.setState({
        todos: newTodos,
        newTodoTitle: ''
      });
    }
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.newTodoTitle}
          onChange={this.handleChange}
          placeholder="添加新的待办事项"
        />
        <button onClick={this.addTodo}>添加</button>
        <ul>
          {this.state.todos.map(todo => (
            <li key={todo.id}>
              {todo.title} - {todo.completed? '已完成' : '未完成'}
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export default TodoList;

addTodo 方法中,创建了一个包含 idtitlecompleted 属性的新对象,然后通过展开运算符添加到 todos 数组中。

  1. 删除复杂列表项
import React, { Component } from'react';

class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [],
      newTodoTitle: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.addTodo = this.addTodo.bind(this);
    this.deleteTodo = this.deleteTodo.bind(this);
  }

  handleChange(event) {
    this.setState({ newTodoTitle: event.target.value });
  }

  addTodo() {
    if (this.state.newTodoTitle.trim()!== '') {
      const newTodo = {
        id: Date.now(),
        title: this.state.newTodoTitle,
        completed: false
      };
      const newTodos = [...this.state.todos, newTodo];
      this.setState({
        todos: newTodos,
        newTodoTitle: ''
      });
    }
  }

  deleteTodo(id) {
    const newTodos = this.state.todos.filter(todo => todo.id!== id);
    this.setState({ todos: newTodos });
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.newTodoTitle}
          onChange={this.handleChange}
          placeholder="添加新的待办事项"
        />
        <button onClick={this.addTodo}>添加</button>
        <ul>
          {this.state.todos.map(todo => (
            <li key={todo.id}>
              {todo.title} - {todo.completed? '已完成' : '未完成'}
              <button onClick={() => this.deleteTodo(todo.id)}>删除</button>
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export default TodoList;

deleteTodo 方法中,根据 id 使用 filter 方法过滤掉要删除的对象。

使用 React 的 key 属性优化列表渲染

在 React 中,当列表项动态变化时,key 属性起着至关重要的作用。key 是 React 用于追踪哪些列表项发生变化、被添加或被移除的辅助标识。

  1. 正确使用 key 在前面的示例中,我们已经使用了 key。比如在遍历待办事项列表时:
{this.state.todos.map((todo, index) => (
  <li key={index}>{todo}</li>
))}

这里使用 index 作为 key,在简单场景下是可行的。但如果列表项的顺序可能改变,或者会频繁增删中间的项,使用 index 作为 key 可能会导致性能问题和渲染异常。更好的做法是使用唯一标识作为 key,比如在复杂列表项的场景下:

{this.state.todos.map(todo => (
  <li key={todo.id}>
    {todo.title} - {todo.completed? '已完成' : '未完成'}
  </li>
))}

使用 todo.id 作为 key 能确保 React 准确地识别每个列表项,高效地进行更新、添加和删除操作。

  1. 错误使用 key 的影响 假设我们有一个可排序的列表,使用 index 作为 key
import React, { Component } from'react';

class SortableList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: ['a', 'b', 'c']
    };
    this.sortItems = this.sortItems.bind(this);
  }

  sortItems() {
    const newItems = this.state.items.slice();
    newItems.sort();
    this.setState({ items: newItems });
  }

  render() {
    return (
      <div>
        <ul>
          {this.state.items.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
        <button onClick={this.sortItems}>排序</button>
      </div>
    );
  }
}

export default SortableList;

当点击排序按钮时,由于 keyindex,React 可能会复用之前的 DOM 元素,导致视觉上看起来排序了,但实际上某些元素的状态可能没有正确更新。如果使用唯一标识作为 key,React 就能正确地重新渲染列表,避免这类问题。

使用 React Hooks 实现动态增删列表项

随着 React Hooks 的引入,函数式组件也能方便地管理状态和副作用。下面用 React Hooks 来实现动态增删列表项。

  1. 添加列表项
import React, { useState } from'react';

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');

  const handleChange = (event) => {
    setNewTodo(event.target.value);
  };

  const addTodo = () => {
    if (newTodo.trim()!== '') {
      setTodos([...todos, newTodo]);
      setNewTodo('');
    }
  };

  return (
    <div>
      <input
        type="text"
        value={newTodo}
        onChange={handleChange}
        placeholder="添加新的待办事项"
      />
      <button onClick={addTodo}>添加</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

通过 useState Hook,我们在函数式组件中定义了 todosnewTodo 状态,以及对应的 setTodossetNewTodo 更新函数。addTodo 函数通过展开运算符添加新的待办事项。

  1. 删除列表项
import React, { useState } from'react';

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');

  const handleChange = (event) => {
    setNewTodo(event.target.value);
  };

  const addTodo = () => {
    if (newTodo.trim()!== '') {
      setTodos([...todos, newTodo]);
      setNewTodo('');
    }
  };

  const deleteTodo = (index) => {
    const newTodos = todos.filter((_, i) => i!== index);
    setTodos(newTodos);
  };

  return (
    <div>
      <input
        type="text"
        value={newTodo}
        onChange={handleChange}
        placeholder="添加新的待办事项"
      />
      <button onClick={addTodo}>添加</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => deleteTodo(index)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

deleteTodo 函数通过 filter 方法删除指定索引的待办事项,并更新 todos 状态。

使用 Redux 管理列表状态及增删操作

Redux 是一个流行的状态管理库,适合用于大型应用中管理复杂的状态。下面以一个待办事项列表为例,介绍如何使用 Redux 实现动态增删列表项。

  1. 安装和配置 Redux 首先,需要安装 reduxreact - redux
npm install redux react-redux

然后创建 Redux 的 storeactionreducer

  1. 定义 Action
// actions/todoActions.js
const ADD_TODO = 'ADD_TODO';
const DELETE_TODO = 'DELETE_TODO';

const addTodo = (title) => ({
  type: ADD_TODO,
  payload: {
    id: Date.now(),
    title,
    completed: false
  }
});

const deleteTodo = (id) => ({
  type: DELETE_TODO,
  payload: id
});

export { addTodo, deleteTodo };

这里定义了 ADD_TODODELETE_TODO 两种 action 类型,以及对应的 action 创建函数。

  1. 定义 Reducer
// reducers/todoReducer.js
const initialState = {
  todos: []
};

const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
       ...state,
        todos: [...state.todos, action.payload]
      };
    case 'DELETE_TODO':
      return {
       ...state,
        todos: state.todos.filter(todo => todo.id!== action.payload)
      };
    default:
      return state;
  }
};

export default todoReducer;

todoReducer 根据不同的 action 类型来更新状态。ADD_TODO 时添加新的待办事项,DELETE_TODO 时删除指定 id 的待办事项。

  1. 创建 Store 并连接到 React 组件
// store.js
import { createStore } from'redux';
import todoReducer from './reducers/todoReducer';

const store = createStore(todoReducer);

export default store;
// App.js
import React from'react';
import { Provider } from'react-redux';
import store from './store';
import TodoList from './components/TodoList';

const App = () => {
  return (
    <Provider store = {store}>
      <TodoList />
    </Provider>
  );
};

export default App;

App.js 中,通过 Providerstore 提供给整个应用。

  1. 在组件中使用 Redux
// components/TodoList.js
import React, { useState } from'react';
import { useDispatch, useSelector } from'react-redux';
import { addTodo, deleteTodo } from '../actions/todoActions';

const TodoList = () => {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();
  const [newTodoTitle, setNewTodoTitle] = useState('');

  const handleChange = (event) => {
    setNewTodoTitle(event.target.value);
  };

  const addNewTodo = () => {
    if (newTodoTitle.trim()!== '') {
      dispatch(addTodo(newTodoTitle));
      setNewTodoTitle('');
    }
  };

  const removeTodo = (id) => {
    dispatch(deleteTodo(id));
  };

  return (
    <div>
      <input
        type="text"
        value={newTodoTitle}
        onChange={handleChange}
        placeholder="添加新的待办事项"
      />
      <button onClick={addNewTodo}>添加</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            {todo.title} - {todo.completed? '已完成' : '未完成'}
            <button onClick={() => removeTodo(todo.id)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

TodoList 组件中,通过 useSelector 获取 todos 状态,通过 useDispatch 派发 action 来实现列表项的增删。

性能优化与注意事项

  1. 避免不必要的重新渲染 在 React 中,状态更新会触发组件的重新渲染。为了避免不必要的重新渲染,可以使用 React.memo 包裹函数式组件,或者在类组件中重写 shouldComponentUpdate 方法。
const MyList = React.memo((props) => {
  // 组件内容
});
class MyList extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 根据 props 和 state 的变化判断是否需要更新
    return nextProps.items!== this.props.items || nextState.someValue!== this.state.someValue;
  }

  render() {
    // 组件内容
  }
}
  1. 数据一致性 在增删列表项时,要确保数据的一致性。比如在删除操作时,要同时更新相关的缓存数据(如果有),避免出现数据不一致的情况。

  2. 事件处理 在处理增删操作的事件时,要注意事件绑定的正确性。特别是在类组件中,需要正确绑定 this,避免出现 this 指向错误的问题。

通过以上多种方法的介绍,相信你对 React 中动态增删列表项的实现有了更深入的理解。在实际开发中,可以根据项目的需求和规模选择合适的方法来实现高效、稳定的列表操作。