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

React State 的初始化与更新机制

2023-10-272.1k 阅读

React State 简介

在 React 应用程序中,State 是一个至关重要的概念。它代表了组件内部的可变数据,这些数据的变化会触发组件的重新渲染,从而更新 UI。与 props(从父组件传递给子组件的数据)不同,state 是组件私有的,并且完全由组件自身控制。通过管理 state,我们可以创建动态、交互式的用户界面。

例如,一个简单的计数器组件,它的状态就是当前的计数数值。每次用户点击按钮增加或减少计数时,该状态就会发生变化,进而导致组件重新渲染以显示最新的计数值。

State 的初始化

  1. 类组件中的 State 初始化 在 React 类组件中,我们通常在 constructor 方法中初始化 state。constructor 是 ES6 类的特殊方法,用于创建和初始化类的实例。
import React, { Component } from'react';

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

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
      </div>
    );
  }
}

export default Counter;

在上述代码中,Counter 组件继承自 React.Component。在 constructor 中,我们首先调用 super(props),这一步是必要的,它会将 props 传递给父类 React.Component 的构造函数。然后,我们通过 this.state 来初始化状态,这里将 count 初始化为 0。

  1. 函数组件中的 State 初始化 在 React 引入 Hooks 之后,函数组件也能够拥有 state 了。我们使用 useState Hook 来实现这一点。useState 是 React 提供的一个内置 Hook,它返回一个数组,数组的第一个元素是当前的 state 值,第二个元素是用于更新该 state 的函数。
import React, { useState } from'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
    </div>
  );
};

export default Counter;

在这个函数组件中,useState(0) 表示我们初始化一个名为 count 的 state,初始值为 0。setCount 则是用于更新 count 状态的函数。

State 的更新机制

  1. 类组件中的 State 更新 在类组件中,我们使用 setState 方法来更新 state。setStateReact.Component 的一个实例方法,它接受一个对象或一个函数作为参数,并将该对象与当前 state 进行合并,从而触发组件的重新渲染。

对象形式的 setState

import React, { Component } from'react';

class Counter 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 Counter;

increment 方法中,我们通过 this.setState 来更新 count 状态。这里 setState 接受一个对象,该对象的 count 属性值为当前 state.count 加 1。

函数形式的 setState 有时候,在更新 state 时,新的 state 值依赖于之前的 state,这时候就需要使用函数形式的 setState。函数形式的 setState 接受一个函数作为参数,该函数的第一个参数是之前的 state,第二个参数是当前的 props。

import React, { Component } from'react';

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

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

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

export default Counter;

在上述代码中,setState 的回调函数接受 prevState,我们基于 prevState.count 来计算新的 count 值。

  1. 函数组件中的 State 更新 在函数组件中,我们使用 useState Hook 返回的更新函数来更新 state。
import React, { useState } from'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

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

export default Counter;

这里 setCount 函数接受新的 count 值,并触发组件重新渲染。

State 更新的批量处理

  1. 类组件中的批量更新 在 React 中,setState 是异步的,并且 React 会批量处理多个 setState 调用,以提高性能。这意味着,在同一事件处理函数中多次调用 setState,React 不会立即更新 state,而是将这些更新合并起来,在事件处理结束后一次性更新 state 并触发重新渲染。
import React, { Component } from'react';

class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value1: 0,
      value2: 0
    };
  }

  handleClick = () => {
    this.setState({ value1: this.state.value1 + 1 });
    this.setState({ value2: this.state.value2 + 1 });
  };

  render() {
    return (
      <div>
        <p>Value1: {this.state.value1}</p>
        <p>Value2: {this.state.value2}</p>
        <button onClick={this.handleClick}>Update</button>
      </div>
    );
  }
}

export default Example;

handleClick 方法中,我们连续调用了两次 setState,但 React 会将这两次更新合并,最终只触发一次重新渲染。

  1. 函数组件中的批量更新 在函数组件中,React 同样会批量处理 useState 的更新。
import React, { useState } from'react';

const Example = () => {
  const [value1, setValue1] = useState(0);
  const [value2, setValue2] = useState(0);

  const handleClick = () => {
    setValue1(value1 + 1);
    setValue2(value2 + 1);
  };

  return (
    <div>
      <p>Value1: {value1}</p>
      <p>Value2: {value2}</p>
      <button onClick={handleClick}>Update</button>
    </div>
  );
};

export default Example;

这里的 setValue1setValue2 调用也会被批量处理,在 handleClick 事件结束后一次性更新并触发重新渲染。

State 更新与组件生命周期

  1. 类组件的生命周期与 State 更新 在类组件中,setState 会触发组件的重新渲染,这涉及到一些生命周期方法。

shouldComponentUpdate shouldComponentUpdate 是一个生命周期方法,它在组件接收到新的 props 或 state 时被调用,用于决定组件是否应该重新渲染。默认情况下,只要 props 或 state 发生变化,组件就会重新渲染。但通过 shouldComponentUpdate,我们可以根据具体的业务逻辑来优化渲染过程。

import React, { Component } from'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: []
    };
  }

  shouldComponentUpdate(nextProps, nextState) {
    // 只有当 state.data 的长度发生变化时才重新渲染
    return nextState.data.length!== this.state.data.length;
  }

  updateData = () => {
    this.setState((prevState) => {
      const newData = [...prevState.data, 'new item'];
      return { data: newData };
    });
  };

  render() {
    return (
      <div>
        <p>Data length: {this.state.data.length}</p>
        <button onClick={this.updateData}>Add item</button>
      </div>
    );
  }
}

export default MyComponent;

在上述代码中,shouldComponentUpdate 方法比较了当前 state 和下一个 state 中 data 的长度,只有长度变化时才允许组件重新渲染。

componentDidUpdate componentDidUpdate 在组件更新后被调用。它可以用于执行一些副作用操作,比如更新 DOM、发送网络请求等。

import React, { Component } from'react';

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

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

  componentDidUpdate(prevProps, prevState) {
    if (this.state.count!== prevState.count) {
      console.log('Count has been updated:', this.state.count);
    }
  }

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

export default MyComponent;

componentDidUpdate 中,我们比较了当前 state 和之前的 state 的 count 值,如果发生变化就打印日志。

  1. 函数组件的副作用与 State 更新 在函数组件中,我们使用 useEffect Hook 来处理副作用操作,类似于类组件的 componentDidMountcomponentDidUpdatecomponentWillUnmount 生命周期方法。
import React, { useState, useEffect } from'react';

const MyComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Count has been updated:', count);
    return () => {
      // 清理函数,类似于 componentWillUnmount
      console.log('Component is unmounting');
    };
  }, [count]);

  const increment = () => {
    setCount(count + 1);
  };

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

export default MyComponent;

在这个函数组件中,useEffect 会在 count 状态发生变化时执行副作用操作(打印日志)。同时,useEffect 返回的函数会在组件卸载时执行,用于清理资源。

State 更新的常见问题与解决方法

  1. 状态更新未触发重新渲染 有时候,我们可能会遇到状态更新了但组件没有重新渲染的情况。这通常是因为没有正确使用 setStateuseState

类组件中错误的更新方式

import React, { Component } from'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: []
    };
  }

  // 错误的更新方式,直接修改 state
  addItemWrong = () => {
    this.state.data.push('new item');
    // 这样不会触发重新渲染
  };

  addItemCorrect = () => {
    this.setState((prevState) => {
      const newData = [...prevState.data, 'new item'];
      return { data: newData };
    });
  };

  render() {
    return (
      <div>
        <p>Data length: {this.state.data.length}</p>
        <button onClick={this.addItemWrong}>Add item (wrong)</button>
        <button onClick={this.addItemCorrect}>Add item (correct)</button>
      </div>
    );
  }
}

export default MyComponent;

addItemWrong 方法中,直接修改 this.state.data 不会触发重新渲染,因为 React 无法检测到这种直接的修改。而 addItemCorrect 使用 setState 来正确更新状态并触发重新渲染。

函数组件中错误的更新方式

import React, { useState } from'react';

const MyComponent = () => {
  const [data, setData] = useState([]);

  // 错误的更新方式,直接修改 state 数组
  const addItemWrong = () => {
    data.push('new item');
    // 这样不会触发重新渲染
  };

  const addItemCorrect = () => {
    setData([...data, 'new item']);
  };

  return (
    <div>
      <p>Data length: {data.length}</p>
      <button onClick={addItemWrong}>Add item (wrong)</button>
      <button onClick={addItemCorrect}>Add item (correct)</button>
    </div>
  );
};

export default MyComponent;

同样,在函数组件中直接修改 data 数组不会触发重新渲染,必须使用 setData 来更新状态。

  1. 多次更新导致不必要的渲染 在某些情况下,频繁的状态更新可能会导致不必要的重新渲染,影响性能。

类组件中的优化 通过 shouldComponentUpdate 方法可以优化类组件的渲染。如前面的例子,通过在 shouldComponentUpdate 中进行条件判断,只在必要时才允许重新渲染。

函数组件中的优化 在函数组件中,可以使用 React.memo 来包裹组件,它类似于类组件的 shouldComponentUpdateReact.memo 会浅比较组件的 props,如果 props 没有变化,组件就不会重新渲染。

import React, { useState } from'react';

const ChildComponent = React.memo((props) => {
  return <p>{props.value}</p>;
});

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <ChildComponent value="Some static value" />
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default ParentComponent;

在这个例子中,ChildComponent 使用 React.memo 包裹,由于它的 props 没有变化,即使父组件 ParentComponentcount 状态更新,ChildComponent 也不会重新渲染。

复杂 State 管理

  1. 使用 Redux 管理复杂 State 当应用程序变得复杂,组件之间的状态管理变得困难时,可以使用 Redux。Redux 遵循单向数据流原则,将应用程序的状态集中管理。

安装 Redux 和 React - Redux 首先,我们需要安装 reduxreact - redux 库。

npm install redux react-redux

创建 Redux Store

import { createStore } from'redux';

// 定义 reducer
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

// 创建 store
const store = createStore(counterReducer);

export default store;

在上述代码中,我们定义了一个 counterReducer,它根据不同的 action.type 来更新 state。然后使用 createStore 创建了 Redux store。

连接 React 组件到 Redux Store

import React from'react';
import ReactDOM from'react-dom';
import { Provider } from'react-redux';
import store from './store';
import Counter from './Counter';

ReactDOM.render(
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);

这里通过 Provider 组件将 Redux store 提供给整个应用程序。

import React from'react';
import { useSelector, useDispatch } from'react-redux';

const Counter = () => {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();

  const increment = () => {
    dispatch({ type: 'INCREMENT' });
  };

  const decrement = () => {
    dispatch({ type: 'DECREMENT' });
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default Counter;

Counter 组件中,我们使用 useSelector 来获取 Redux store 中的状态,使用 useDispatch 来分发 action 以更新状态。

  1. 使用 MobX 管理复杂 State MobX 是另一个流行的状态管理库,它采用响应式编程的理念。

安装 MobX 和 MobX - React

npm install mobx mobx - react

创建 MobX Store

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

class CounterStore {
  count = 0;

  constructor() {
    makeObservable(this, {
      count: observable,
      increment: action,
      decrement: action
    });
  }

  increment = () => {
    this.count++;
  };

  decrement = () => {
    this.count--;
  };
}

const counterStore = new CounterStore();

export default counterStore;

在这个 CounterStore 中,我们使用 makeObservable 来标记 count 为可观察的状态,incrementdecrement 为可触发状态变化的动作。

连接 React 组件到 MobX Store

import React from'react';
import ReactDOM from'react-dom';
import { Provider } from'mobx - react';
import counterStore from './counterStore';
import Counter from './Counter';

ReactDOM.render(
  <Provider counterStore={counterStore}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);

这里通过 Provider 组件将 counterStore 提供给应用程序。

import React from'react';
import { observer } from'mobx - react';
import counterStore from './counterStore';

const Counter = observer(() => {
  return (
    <div>
      <p>Count: {counterStore.count}</p>
      <button onClick={() => counterStore.increment()}>Increment</button>
      <button onClick={() => counterStore.decrement()}>Decrement</button>
    </div>
  );
});

export default Counter;

Counter 组件中,使用 observer 函数将组件包装成响应式组件,它会自动订阅 counterStore 的变化并重新渲染。

总结 State 初始化与更新机制

React 的 State 初始化与更新机制是构建动态和交互式用户界面的核心。无论是类组件还是函数组件,正确的初始化和更新状态是确保应用程序正常运行的关键。在类组件中,通过 constructor 初始化 state,并使用 setState 来更新状态,同时可以利用生命周期方法来控制渲染和处理副作用。在函数组件中,借助 useState Hook 初始化和更新状态,useEffect Hook 处理副作用。当应用程序变得复杂时,可选择 Redux 或 MobX 等状态管理库来更好地管理状态。理解这些机制,并能够灵活运用,将有助于开发高效、可维护的 React 应用程序。