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

React 列表与状态管理的结合

2022-07-127.8k 阅读

React 列表基础

在 React 开发中,列表是非常常见的展示形式。无论是展示用户列表、商品列表还是其他数据集合,都需要用到列表。React 提供了简洁而强大的方式来处理列表。

在 React 中,我们通常使用 JavaScript 的数组方法来生成列表元素。例如,假设有一个数组 items = [1, 2, 3],我们想要将其渲染为无序列表。代码如下:

import React from 'react';

function ListExample() {
    const items = [1, 2, 3];
    return (
        <ul>
            {items.map((item) => (
                <li key={item}>{item}</li>
            ))}
        </ul>
    );
}

export default ListExample;

这里使用了数组的 map 方法,map 方法会遍历数组中的每一个元素,并返回一个新的数组。在返回的新数组中,每个元素都是一个 <li> 标签,这就组成了我们需要的列表。

key 的重要性

在上述代码中,<li> 标签上有一个 key 属性。key 是 React 用于追踪哪些列表项被修改、添加或删除的一个特殊属性。它在列表渲染中起着至关重要的作用。

当 React 渲染列表时,它会利用 key 来高效地更新 DOM。如果没有 key,React 可能会错误地重新渲染整个列表,而不是只更新变化的部分。这会导致性能问题,尤其是在列表项较多的情况下。

key 应该是列表项的唯一标识符。在上面的例子中,因为数组元素是唯一的数字,所以直接使用数组元素作为 key。但在实际应用中,通常会使用数据对象中的唯一属性,比如数据库中的 id。例如:

import React from 'react';

function UserList() {
    const users = [
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' },
        { id: 3, name: 'Charlie' }
    ];
    return (
        <ul>
            {users.map((user) => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}

export default UserList;

状态管理简介

状态管理在 React 应用中是必不可少的部分。它用于管理应用中的数据变化。React 组件有自己的状态(state),可以通过 setState(类组件)或 useState(函数组件)来更新。

函数组件中的 useState

在函数组件中,useState 是 React 提供的用于添加状态的 Hook。它接收一个初始状态值,并返回一个数组,数组的第一个元素是当前状态值,第二个元素是用于更新状态的函数。例如:

import React, { useState } from'react';

function Counter() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
    );
}

export default Counter;

这里 useState(0) 初始化了一个名为 count 的状态,初始值为 0。setCount 函数用于更新 count 的值。当点击按钮时,setCount(count + 1) 会将 count 的值加 1,从而触发组件重新渲染,页面上显示的 count 值也会更新。

类组件中的 setState

在类组件中,使用 setState 方法来更新状态。与 useState 不同,setState 会合并新的状态到当前状态。例如:

import React, { Component } from'react';

class CounterClass extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }

    increment = () => {
        this.setState({
            count: this.state.count + 1
        });
    };

    render() {
        return (
            <div>
                <p>Count: {this.state.count}</p>
                <button onClick={this.increment}>Increment</button>
            </div>
        );
    }
}

export default CounterClass;

increment 方法中,this.setState 方法会将新的 count 值合并到当前状态中,从而触发组件重新渲染。

React 列表与状态管理的简单结合

将列表与状态管理结合起来,可以实现动态的列表操作。比如,我们可以在列表中添加或删除项目。

添加列表项

假设我们有一个待办事项列表,用户可以输入新的事项并添加到列表中。代码如下:

import React, { useState } from'react';

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

    const handleInputChange = (e) => {
        setNewTodo(e.target.value);
    };

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

    return (
        <div>
            <input
                type="text"
                placeholder="Enter a new todo"
                value={newTodo}
                onChange={handleInputChange}
            />
            <button onClick={handleAddTodo}>Add Todo</button>
            <ul>
                {todos.map((todo, index) => (
                    <li key={index}>{todo}</li>
                ))}
            </ul>
        </div>
    );
}

export default TodoList;

在这段代码中,todos 状态用于存储所有的待办事项,newTodo 状态用于存储用户在输入框中输入的新事项。handleInputChange 函数用于更新 newTodo 的值,handleAddTodo 函数用于将新事项添加到 todos 列表中。当点击 “Add Todo” 按钮时,如果 newTodo 不为空,就会将其添加到 todos 数组中,并清空 newTodo

删除列表项

在上述待办事项列表的基础上,我们可以添加删除功能。代码如下:

import React, { useState } from'react';

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

    const handleInputChange = (e) => {
        setNewTodo(e.target.value);
    };

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

    const handleDeleteTodo = (index) => {
        const newTodos = [...todos];
        newTodos.splice(index, 1);
        setTodos(newTodos);
    };

    return (
        <div>
            <input
                type="text"
                placeholder="Enter a new todo"
                value={newTodo}
                onChange={handleInputChange}
            />
            <button onClick={handleAddTodo}>Add Todo</button>
            <ul>
                {todos.map((todo, index) => (
                    <li key={index}>
                        {todo}
                        <button onClick={() => handleDeleteTodo(index)}>Delete</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default TodoList;

这里添加了 handleDeleteTodo 函数,它接收要删除的列表项的索引。通过 splice 方法从 todos 数组的副本中删除指定索引的项,然后使用 setTodos 更新 todos 状态,从而实现删除列表项的功能。

更复杂的列表状态管理场景

在实际项目中,列表状态管理可能会更加复杂。比如,列表项可能有多个属性,并且需要对这些属性进行编辑、排序等操作。

列表项属性编辑

假设我们有一个用户列表,每个用户有姓名和年龄。用户可以编辑这些信息。代码如下:

import React, { useState } from'react';

function UserList() {
    const [users, setUsers] = useState([
        { id: 1, name: 'Alice', age: 25 },
        { id: 2, name: 'Bob', age: 30 },
        { id: 3, name: 'Charlie', age: 35 }
    ]);

    const [editingUserId, setEditingUserId] = useState(null);
    const [editedName, setEditedName] = useState('');
    const [editedAge, setEditedAge] = useState('');

    const handleEdit = (userId) => {
        const user = users.find(u => u.id === userId);
        setEditingUserId(userId);
        setEditedName(user.name);
        setEditedAge(user.age);
    };

    const handleSave = () => {
        if (editedName.trim()!== '' &&!isNaN(editedAge)) {
            const newUsers = users.map(user => {
                if (user.id === editingUserId) {
                    return {
                       ...user,
                        name: editedName,
                        age: parseInt(editedAge)
                    };
                }
                return user;
            });
            setUsers(newUsers);
            setEditingUserId(null);
            setEditedName('');
            setEditedAge('');
        }
    };

    const handleCancel = () => {
        setEditingUserId(null);
        setEditedName('');
        setEditedAge('');
    };

    return (
        <div>
            <ul>
                {users.map(user => (
                    <li key={user.id}>
                        {editingUserId === user.id? (
                            <>
                                <input
                                    type="text"
                                    value={editedName}
                                    onChange={(e) => setEditedName(e.target.value)}
                                />
                                <input
                                    type="number"
                                    value={editedAge}
                                    onChange={(e) => setEditedAge(e.target.value)}
                                />
                                <button onClick={handleSave}>Save</button>
                                <button onClick={handleCancel}>Cancel</button>
                            </>
                        ) : (
                            <>
                                {user.name} - {user.age}
                                <button onClick={() => handleEdit(user.id)}>Edit</button>
                            </>
                        )}
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default UserList;

在这段代码中,editingUserId 用于记录当前正在编辑的用户的 id。当点击 “Edit” 按钮时,handleEdit 函数会获取当前用户的信息并设置到 editedNameeditedAge 状态中。用户在输入框中修改信息后,点击 “Save” 按钮,handleSave 函数会更新 users 列表中的相应用户信息。点击 “Cancel” 按钮则会取消编辑状态。

列表排序

继续以上面的用户列表为例,我们可以添加排序功能,根据用户年龄进行升序或降序排列。代码如下:

import React, { useState } from'react';

function UserList() {
    const [users, setUsers] = useState([
        { id: 1, name: 'Alice', age: 25 },
        { id: 2, name: 'Bob', age: 30 },
        { id: 3, name: 'Charlie', age: 35 }
    ]);

    const [sortOrder, setSortOrder] = useState('asc');

    const handleSort = () => {
        if (sortOrder === 'asc') {
            setUsers([...users].sort((a, b) => a.age - b.age));
            setSortOrder('desc');
        } else {
            setUsers([...users].sort((a, b) => b.age - a.age));
            setSortOrder('asc');
        }
    };

    return (
        <div>
            <button onClick={handleSort}>
                Sort by Age {sortOrder === 'asc'? '(Asc)' : '(Desc)'}
            </button>
            <ul>
                {users.map(user => (
                    <li key={user.id}>
                        {user.name} - {user.age}
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default UserList;

这里 sortOrder 状态用于记录当前的排序顺序,初始为升序。当点击 “Sort by Age” 按钮时,handleSort 函数会根据当前的 sortOrderusers 数组进行排序,并切换 sortOrder 的值。

使用 Redux 进行列表状态管理

虽然 React 自身的状态管理在许多场景下已经足够,但对于大型应用,使用 Redux 可以更好地管理复杂的状态。

Redux 基础概念

Redux 有三个核心概念:store、action 和 reducer。

Store:存储应用的所有状态,一个 Redux 应用只有一个 store。可以通过 createStore 函数创建。

Action:用于描述发生的事件,是一个普通的 JavaScript 对象,必须有一个 type 属性来表示事件类型。

Reducer:是一个纯函数,接收当前状态和 action,返回新的状态。根据 action 的 type 来决定如何更新状态。

在 React 中使用 Redux 管理列表状态

以之前的待办事项列表为例,使用 Redux 进行状态管理。

首先,安装 reduxreact - redux

npm install redux react-redux

创建 actions/todoActions.js

const ADD_TODO = 'ADD_TODO';
const DELETE_TODO = 'DELETE_TODO';

export const addTodo = (todo) => ({
    type: ADD_TODO,
    payload: todo
});

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

export { ADD_TODO, DELETE_TODO };

创建 reducers/todoReducer.js

import { ADD_TODO, DELETE_TODO } from '../actions/todoActions';

const initialState = [];

const todoReducer = (state = initialState, action) => {
    switch (action.type) {
        case ADD_TODO:
            return [...state, action.payload];
        case DELETE_TODO:
            const newState = [...state];
            newState.splice(action.payload, 1);
            return newState;
        default:
            return state;
    }
};

export default todoReducer;

创建 store.js

import { createStore } from'redux';
import todoReducer from './reducers/todoReducer';

const store = createStore(todoReducer);

export default store;

App.js 中使用 Redux:

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

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

export default App;

components/TodoList.js 中:

import React, { useState } from'react';
import { useDispatch, useSelector } from'react-redux';
import { addTodo, deleteTodo } from '../actions/todoActions';

function TodoList() {
    const todos = useSelector(state => state);
    const dispatch = useDispatch();
    const [newTodo, setNewTodo] = useState('');

    const handleInputChange = (e) => {
        setNewTodo(e.target.value);
    };

    const handleAddTodo = () => {
        if (newTodo.trim()!== '') {
            dispatch(addTodo(newTodo));
            setNewTodo('');
        }
    };

    const handleDeleteTodo = (index) => {
        dispatch(deleteTodo(index));
    };

    return (
        <div>
            <input
                type="text"
                placeholder="Enter a new todo"
                value={newTodo}
                onChange={handleInputChange}
            />
            <button onClick={handleAddTodo}>Add Todo</button>
            <ul>
                {todos.map((todo, index) => (
                    <li key={index}>
                        {todo}
                        <button onClick={() => handleDeleteTodo(index)}>Delete</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default TodoList;

在上述代码中,通过 useSelector 获取 Redux store 中的状态,通过 useDispatch 获取 dispatch 函数,用于派发 action 来更新状态。addTododeleteTodo action 会触发 todoReducer 中的相应逻辑,从而更新待办事项列表的状态。

使用 MobX 进行列表状态管理

MobX 是另一种流行的状态管理库,与 Redux 不同,它采用响应式编程思想。

MobX 基础概念

Observable:可观察的数据,当数据变化时会自动通知依赖它的组件。

Observer:观察可观察数据的组件,当可观察数据变化时会自动重新渲染。

Action:用于修改可观察数据的函数,类似于 Redux 中的 action,但在 MobX 中,action 主要用于标记会引起状态变化的操作。

在 React 中使用 MobX 管理列表状态

同样以待办事项列表为例。

首先,安装 mobxmobx - react

npm install mobx mobx - react

创建 stores/TodoStore.js

import { makeObservable, observable, action } from'mobx';

class TodoStore {
    todos = [];

    constructor() {
        makeObservable(this, {
            todos: observable,
            addTodo: action,
            deleteTodo: action
        });
    }

    addTodo(todo) {
        this.todos.push(todo);
    }

    deleteTodo(index) {
        this.todos.splice(index, 1);
    }
}

const todoStore = new TodoStore();
export default todoStore;

App.js 中使用 MobX:

import React from'react';
import { Provider } from'mobx - react';
import TodoList from './components/TodoList';
import todoStore from './stores/TodoStore';

function App() {
    return (
        <Provider todoStore = {todoStore}>
            <TodoList />
        </Provider>
    );
}

export default App;

components/TodoList.js 中:

import React, { useState } from'react';
import { observer } from'mobx - react';
import todoStore from '../stores/TodoStore';

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

    const handleInputChange = (e) => {
        setNewTodo(e.target.value);
    };

    const handleAddTodo = () => {
        if (newTodo.trim()!== '') {
            todoStore.addTodo(newTodo);
            setNewTodo('');
        }
    };

    const handleDeleteTodo = (index) => {
        todoStore.deleteTodo(index);
    };

    return (
        <div>
            <input
                type="text"
                placeholder="Enter a new todo"
                value={newTodo}
                onChange={handleInputChange}
            />
            <button onClick={handleAddTodo}>Add Todo</button>
            <ul>
                {todoStore.todos.map((todo, index) => (
                    <li key={index}>
                        {todo}
                        <button onClick={() => handleDeleteTodo(index)}>Delete</button>
                    </li>
                ))}
            </ul>
        </div>
    );
});

export default TodoList;

在上述代码中,通过 observer 函数将 TodoList 组件包裹,使其成为一个观察组件,能够自动响应 todoStore 中数据的变化。todoStore 中的 addTododeleteTodo 方法直接修改 todos 数组,触发组件重新渲染。

总结 React 列表与状态管理结合的要点

  1. 列表渲染:使用数组的 map 方法生成列表元素,并为每个列表项提供唯一的 key
  2. React 自身状态管理:函数组件用 useState,类组件用 setState,实现简单的列表动态操作,如添加、删除列表项。
  3. 复杂场景处理:对于列表项属性编辑、排序等复杂操作,要合理组织状态和逻辑。
  4. 状态管理库:Redux 适用于大型应用,通过 store、action 和 reducer 管理状态;MobX 采用响应式编程,通过 observable、observer 和 action 管理状态。根据项目需求选择合适的状态管理方案。

无论是简单的列表展示还是复杂的列表交互,结合有效的状态管理,都能让 React 应用更加健壮和易于维护。在实际开发中,需要根据项目的规模、复杂度以及团队的技术栈来选择最适合的方式来实现列表与状态管理的结合。