Post

Redux 在 React 16 中的使用变更

Redux 在 React 16 中的使用变更

React 16 对 React-Redux 的影响

1. 生命周期方法的废弃与替代

React 16 废弃了一些 React 15 中常用的生命周期方法,这会影响 React-Redux 组件的状态更新逻辑。

  • 废弃方法:
    • componentWillMount
    • componentWillReceiveProps
    • componentWillUpdate
  • 原因: Fiber 架构引入了异步渲染,这些方法可能在未来的 React 版本中多次调用,导致不可预测的行为(如在 componentWillMount 中发起异步请求可能导致重复请求)。
  • 替代方案:
    • componentWillMountconstructorcomponentDidMount:将初始化逻辑移到构造函数或 componentDidMount 中。例如,Redux 相关的初始化(如 dispatch 一个 action)应放在 componentDidMount

      1
      2
      3
      
      componentDidMount() {
        this.props.fetchData(); // 发起数据请求
      }
      
    • componentWillReceivePropsstatic getDerivedStateFromProps:React 16 引入的新静态生命周期方法,用于从 props 派生 state:
      1
      2
      3
      4
      5
      6
      
      static getDerivedStateFromProps(nextProps, prevState) {
        if (nextProps.someValue !== prevState.someValue) {
          return { someValue: nextProps.someValue };
        }
        return null;
      }
      

      注意:getDerivedStateFromProps 是静态方法,不能访问 this,也不建议直接 dispatch action,而是通过 componentDidUpdate 处理副作用:

      1
      2
      3
      4
      5
      
      componentDidUpdate(prevProps) {
        if (prevProps.someValue !== this.props.someValue) {
          this.props.updateData(this.props.someValue);
        }
      }
      
    • componentWillUpdatecomponentDidUpdate:将更新逻辑移到 componentDidUpdate

React-Redux 影响: React-Redux 的 connect 会将 Redux Store 的 state 映射到 props 中,组件可能依赖 componentWillReceiveProps 监听 props 变化(如 this.props.items)来更新本地 state 或触发 action。在 React 16 中,需改用 getDerivedStateFromPropscomponentDidUpdate,否则可能导致逻辑失效。

2. 错误边界的引入

React 16 引入了错误边界(Error Boundaries),通过 componentDidCatch 捕获渲染过程中的错误。这对 React-Redux 组件有以下影响:

  • 捕获 Redux 相关错误: 如果 mapStateToProps 或组件渲染过程中抛出异常(如访问不存在的 state 属性),React 15 会导致整个应用崩溃,而 React 16 可以通过错误边界捕获:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    class ErrorBoundary extends React.Component {
      state = { hasError: false };
    
      componentDidCatch(error, info) {
        this.setState({ hasError: true });
        console.log(error, info);
      }
    
      render() {
        if (this.state.hasError) {
          return <h1>发生错误,请稍后重试</h1>;
        }
        return this.props.children;
      }
    }
    
    // 使用
    <ErrorBoundary>
      <MyConnectedComponent />
    </ErrorBoundary>
    
  • 建议: 在 React-Redux 组件外层包裹错误边界,确保 Redux 状态异常不会影响整个应用。

3. setState 的行为变更

React 16 对 setState 的处理更偏向异步批处理,尤其在事件处理中:

  • React 15:setState 可能是同步更新,尤其在非 React 事件(如 setTimeout)中。
  • React 16:setState 默认异步批处理,统一在 Fiber 调度中更新。

React-Redux 影响: 如果组件通过 setState 和 Redux 的 dispatch 混合管理状态,需注意 setState 的异步性。例如:

1
2
3
4
5
handleClick() {
  this.setState({ loading: true }); // 异步更新
  this.props.fetchData(); // dispatch action
  console.log(this.state.loading); // 可能仍为 false
}

解决方法: 使用回调或 componentDidUpdate 确保顺序:

1
2
3
4
5
handleClick() {
  this.setState({ loading: true }, () => {
    this.props.fetchData();
  });
}

4. React-Redux 5.x 的兼容性与优化

React-Redux 5.x 完全支持 React 16,但有一些新特性和优化:

  • 性能优化: React-Redux 5.x 优化了 connect 的性能,减少不必要的重新渲染。确保 mapStateToPropsmapDispatchToProps 是纯函数,避免每次返回新对象:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    // 错误:每次返回新对象,导致不必要的渲染
    const mapStateToProps = state => ({
      items: state.items.map(item => ({ ...item }))
    });
    
    // 正确:保持引用稳定
    const mapStateToProps = state => ({
      items: state.items
    });
    
  • Provider 增强: <Provider> 仍然是 React-Redux 的核心组件,React 16 支持新的 React Context API,但 React-Redux 5.x 仍使用旧 Context API,需注意潜在的 Context 冲突。

React 16 + React-Redux 示例

以下是一个简单的 Todo 应用,展示 React 16 和 React-Redux 的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// TodoApp.js
import React, { Component } from 'react';
import { connect } from 'react-redux';

class TodoApp extends Component {
  state = { inputValue: '' };

  static getDerivedStateFromProps(nextProps, prevState) {
    // 示例:从 props 派生 state
    if (nextProps.todos.length === 0 && prevState.inputValue !== '') {
      return { inputValue: '' };
    }
    return null;
  }

  handleInputChange = e => {
    this.setState({ inputValue: e.target.value });
  };

  handleAddTodo = () => {
    if (this.state.inputValue) {
      this.props.addTodo(this.state.inputValue);
      this.setState({ inputValue: '' });
    }
  };

  componentDidUpdate(prevProps) {
    if (prevProps.todos.length !== this.props.todos.length) {
      console.log('Todos updated:', this.props.todos);
    }
  }

  render() {
    const { todos } = this.props;
    return (
      <div>
        <h1>Todo List</h1>
        <input
          value={this.state.inputValue}
          onChange={this.handleInputChange}
        />
        <button onClick={this.handleAddTodo}>Add Todo</button>
        <ul>
          {todos.map(todo => (
            <li key={todo.id}>{todo.text}</li>
          ))}
        </ul>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  todos: state.todos
});

const mapDispatchToProps = dispatch => ({
  addTodo: text => dispatch({ type: 'ADD_TODO', payload: { id: Date.now(), text } })
});

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import TodoApp from './TodoApp';

const initialState = { todos: [] };
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return { ...state, todos: [...state.todos, action.payload] };
    default:
      return state;
  }
};

const store = createStore(reducer);

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

变更点:

  • 使用 getDerivedStateFromProps 替代 componentWillReceiveProps
  • componentDidUpdate 中处理副作用。
  • 确保 mapStateToProps 保持引用稳定。

注意事项与建议

  1. 检查生命周期依赖:如果你的 React 15 项目依赖 componentWillMountcomponentWillReceiveProps,需尽快迁移到 React 16 的新生命周期方法。
  2. 调试工具:使用 React Developer Tools 和 Redux DevTools 检查组件渲染和状态变化。
  3. 测试兼容性:React 16 的 Fiber 架构可能影响组件的渲染顺序,建议全面测试。
  4. 升级 React-Redux:确保使用 React-Redux 5.x 版本可能不完全兼容 React 16。

从 React 15 到 React 16,生命周期方法的废弃和 Fiber 架构的引入对 React-Redux 的使用有一定影响。开发者需要适应新的生命周期方法(如 getDerivedStateFromProps)、错误边界和异步 setState 的行为,同时优化 mapStateToProps 的性能。React-Redux 5.x 提供了良好的兼容性,但在开发 React 16 项目时,理解这些变更将帮助你避免潜在问题。

This post is licensed under CC BY 4.0 by the author.