首页 > 其他分享 >react 总结+复习+应用加深

react 总结+复习+应用加深

时间:2024-10-26 11:21:12浏览次数:8  
标签:React return 复习 react state 加深 props 组件 const

文章目录

一、React生命周期

1. 挂载阶段(Mounting)

  • constructor(props)
    • 执行时机:组件创建时调用,是类组件中第一个被调用的方法。
    • 作用
      • 初始化组件的state,通过this.state来设置初始状态。例如:this.state = { count: 0 };初始化一个计数器状态。
      • 绑定事件处理函数。由于在JavaScript中,类方法默认不会绑定this,所以需要手动绑定。如this.handleClick = this.handleClick.bind(this);
    • 注意事项
      • 必须先调用super(props),这是JavaScript类继承的要求,用于初始化父类的构造函数,确保组件能够正确访问props
  • componentWillMount(已废弃)
    • 执行时机:在组件挂载到DOM之前,render方法之前调用。
    • 废弃原因:在异步渲染等场景下可能导致问题,并且它的一些功能可以被其他生命周期方法替代,如componentDidMount
  • render()
    • 执行时机:挂载阶段和更新阶段都会调用。
    • 作用
      • 用于描述组件的UI结构,返回一个React元素,这个元素可以是原生DOM元素(如<div><p>等)或者是其他自定义组件。例如:return <div>Hello, World!</div>;
      • 根据组件的propsstate生成虚拟DOM(Virtual DOM),React会根据虚拟DOM来更新真实DOM。
    • 注意事项
      • 应该是一个纯函数,意味着它不应该修改组件的状态(this.state),也不应该有其他副作用,如发送网络请求、修改DOM等。如果在render中修改状态,可能会导致无限循环的更新。
  • componentDidMount()
    • 执行时机:组件挂载到DOM之后立即调用。
    • 作用
      • 适合进行需要DOM节点的操作。例如,通过document.getElementById等DOM API获取DOM元素并进行操作。
      • 发送网络请求获取数据来填充组件内容,因为此时组件已经挂载到DOM,可以安全地更新组件状态。例如:
componentDidMount() {
    fetch('https://example.com/api/data')
      .then(response => response.json())
      .then(data => this.setState({ data }));
}
  • 初始化第三方JavaScript库。比如使用Chart.js绘制图表,需要在DOM节点存在后进行初始化。
    - 注意事项
    • 注意清理在这个方法中创建的资源,如定时器、订阅等。可以在componentWillUnmount中进行清理。

补充

  1. static getDerivedStateFromProps

    • 在组件挂载阶段,static getDerivedStateFromProps是在constructor之后、render之前被调用。这使得它能够在组件首次渲染之前,根据传入的propsstate进行初始化或者调整。
    • 例如,在以下代码中:
    class MyComponent extends React.Component {
        static getDerivedStateFromProps(props, state) {
            console.log('getDerivedStateFromProps called during mount');
            if (!state.initialized) {
                return {
                    initialized: true,
                    value: props.initialValue
                };
            }
            return null;
        }
        constructor(props) {
            super(props);
            console.log('Constructor called');
            this.state = {
                initialized: false
            };
        }
        render() {
            console.log('Render called');
            return <div>{this.state.value}</div>;
        }
    }
    
    • 当组件挂载时,控制台会先打印Constructor called,然后是getDerivedStateFromProps called during mount,最后是Render called。可以看到getDerivedStateFromPropsconstructorrender之间执行,用于根据props来初始化state中的value属性,并设置initializedtrue
  2. 根据props初始化state

    • 应用场景:当组件的初始状态依赖于从父组件传递过来的props时,getDerivedStateFromProps是一个很好的工具。例如,一个显示用户信息的组件,其初始状态可能需要根据父组件传递的用户数据来设置。
    • 示例代码
    class UserProfile extends React.Component {
        static getDerivedStateFromProps(props, state) {
            if (!state.userData) {
                return {
                    userData: props.user
                };
            }
            return null;
        }
        constructor(props) {
            super(props);
            this.state = {};
        }
        render() {
            return (
                <div>
                    <p>Name: {this.state.userData.name}</p>
                    <p>Age: {this.state.userData.age}</p>
                </div>
            );
        }
    }
    
    • 在这个UserProfile组件中,state中的userData初始值是从props获取的。getDerivedStateFromProps检查state中是否已经有userData,如果没有(组件首次挂载时),就将props.user赋值给state.userData,然后在render函数中使用这个状态来显示用户的姓名和年龄。
  3. 处理特殊的初始状态设置需求

    • 应用场景:有时候,组件可能需要根据props进行一些特殊的初始状态设置,比如对props进行转换或者验证后再存入state。例如,一个组件接收一个日期字符串格式的props,但在组件内部需要将其转换为日期对象存储在state中。
    • 示例代码
    class DateDisplay extends React.Component {
        static getDerivedStateFromProps(props, state) {
            if (!state.dateObject) {
                const date = new Date(props.dateString);
                if (!isNaN(date.getTime())) {
                    return {
                        dateObject: date
                    };
                } else {
                    return {
                        dateObject: null,
                        error: 'Invalid date format'
                    };
                }
            }
            return null;
        }
        constructor(props) {
            super(props);
            this.state = {};
        }
        render() {
            if (this.state.error) {
                return <p>{this.state.error}</p>;
            } else if (this.state.dateObject) {
                return <p>{this.state.dateObject.toDateString()}</p>;
            }
            return null;
        }
    }
    
    • DateDisplay组件中,getDerivedStateFromProps检查state中是否已经有dateObject。如果没有(首次挂载时),它会尝试将props.dateString转换为日期对象。如果转换成功,就将日期对象存入state.dateObject;如果转换失败,就设置state.error来显示错误信息。然后在render函数中根据state的情况来显示日期或者错误信息。

2. 更新阶段(Updating)

  • componentWillReceiveProps(nextProps)(已废弃)
    • 执行时机:在组件接收到新的props时被调用,在render方法之前。
    • 废弃原因:可能会导致性能问题和意外行为,特别是在父组件频繁更新props的情况下。React团队推荐使用getDerivedStateFromProps作为替代。
  • shouldComponentUpdate(nextProps, nextState)
    • 执行时机:在组件接收到新的props或者state更新之前被调用。
    • 作用
      • 用于性能优化。可以根据nextPropsnextState与当前propsstate的比较,决定组件是否需要重新渲染。例如:
shouldComponentUpdate(nextProps, nextState) {
    return nextProps.visible!== this.props.visible;
}
    - 只有当返回`true`时,组件才会继续更新流程(调用`render`等后续方法)。
- **注意事项**:
    - 需要谨慎使用,因为错误的返回值可能导致组件不更新或者过度更新。
  • componentWillUpdate(nextProps, nextState)(已废弃)

    • 执行时机:在组件更新之前被调用,在shouldComponentUpdate之后,render之前。
    • 废弃原因:和componentWillReceiveProps类似,可能导致难以预测的副作用,在异步渲染场景下会出现问题。
  • render()(同挂载阶段的render

  • getSnapshotBeforeUpdate(prevProps, prevState)

    1. 阶段位置
    • getSnapshotBeforeUpdate生命周期方法在组件更新阶段被调用,具体是在render方法之后,componentDidUpdate之前。这个位置使得它能够获取到组件更新前的最后一个“快照”(DOM状态或其他相关信息),并将其传递给componentDidUpdate
    1. 应用场景
    • 保存滚动位置

      • 场景描述:在一个可滚动的列表组件中,当列表数据更新(例如添加或删除了列表项)时,为了保持用户的滚动位置,需要在更新前获取滚动位置,然后在更新后恢复滚动位置。
      • 示例代码
      class ScrollableList extends React.Component {
          constructor(props) {
              super(props);
              this.listRef = React.createRef();
              this.state = {
                  items: [],
                  scrollTop: 0
              };
          }
          componentDidMount() {
              // 模拟获取初始数据
              this.setState({
                  items: [1, 2, 3, 4, 5]
              });
          }
          handleAddItem = () => {
              const newItems = [...this.state.items, this.state.items.length + 1];
              this.setState({
                  items: newItems
              });
          };
          getSnapshotBeforeUpdate(prevProps, prevState) {
              if (this.listRef.current) {
                  return this.listRef.current.scrollTop;
              }
              return null;
          }
          componentDidUpdate(prevProps, prevState, snapshot) {
              if (snapshot!== null) {
                  this.listRef.current.scrollTop = snapshot;
              }
          }
          render() {
              return (
                  <div ref={this.listRef} style={{ height: '200px', overflow: 'auto' }}>
                      <ul>
                          {this.state.items.map((item) => (
                              <li key={item}>{item}</li>
                          ))}
                      </ul>
                      <button onClick={this.handleAddItem}>Add Item</button>
                  </div>
              );
          }
      }
      
      • 解释:在这个ScrollableList组件中,getSnapshotBeforeUpdate用于在列表更新前获取div元素(通过ref获取)的滚动位置scrollTop。这个滚动位置信息作为snapshot参数传递给componentDidUpdate,在componentDidUpdate中恢复滚动位置,从而实现当列表更新时,用户的滚动位置保持不变。
    • 记录元素尺寸变化

      • 场景描述:当组件中的某个元素尺寸(如宽度、高度)因为状态或属性更新而发生变化时,可以在getSnapshotBeforeUpdate中记录旧的尺寸,然后在componentDidUpdate中比较新旧尺寸,执行相应的操作,例如调整其他元素的布局。
      • 示例代码
      class ResizableComponent extends React.Component {
          constructor(props) {
              super(props);
              this.componentRef = React.createRef();
              this.state = {
                  width: '100px',
                  height: '100px'
              };
          }
          handleResize = () => {
              this.setState((prevState) => ({
                  width: `${prevState.width.slice(0, -2) * 1.2}px`,
                  height: `${prevState.height.slice(0, -2) * 1.2}px`
              }));
          };
          getSnapshotBeforeUpdate(prevProps, prevState) {
              if (this.componentRef.current) {
                  return {
                      prevWidth: this.componentRef.current.offsetWidth,
                      prevHeight: this.componentRef.current.offsetHeight
                  };
              }
              return null;
          }
          componentDidUpdate(prevProps, prevState, snapshot) {
              if (snapshot) {
                  const widthChange = this.componentRef.current.offsetWidth - snapshot.prevWidth;
                  const heightChange = this.componentRef.current.offsetHeight - snapshot.prevHeight;
                  console.log(`Width change: ${widthChange}px, Height change: ${heightChange}px`);
              }
          }
          render() {
              return (
                  <div
                      ref={this.componentRef}
                      style={{
                          width: this.state.width,
                          height: this.state.height,
                          backgroundColor: 'lightblue',
                          cursor: 'pointer'
                      }}
                      onClick={this.handleResize}
                  />
              );
          }
      }
      
      • 解释:在ResizableComponent组件中,getSnapshotBeforeUpdate获取组件div元素(通过ref获取)更新前的宽度和高度。在componentDidUpdate中,通过比较更新前后的宽度和高度,计算出尺寸变化,并打印到控制台。这可以用于进一步的布局调整或其他与尺寸变化相关的操作。
  • componentDidUpdate(prevProps, prevState)

    • 执行时机:在组件更新后被调用,在render之后。
    • 作用
      • 可以根据更新前后的propsstate进行操作。例如,比较prevPropsthis.props来判断某个属性是否改变,然后执行相应的操作。
      • 对DOM进行操作,如更新第三方库的配置等。
    • 注意事项
      • 注意避免在这个方法中引起无限循环更新。如果在componentDidUpdate中更新状态,并且没有正确的条件限制,可能会导致组件不断地重新渲染。

3. 卸载阶段(Unmounting)

  • componentWillUnmount()
    • 执行时机:在组件从DOM中移除之前被调用。
    • 作用
      • 用于清理在componentDidMount或其他生命周期中创建的副作用。例如:
        • 清除定时器:clearInterval(this.timer);,如果在componentDidMount中设置了定时器,需要在这里清除。
        • 取消网络请求:如果使用了一些没有自动取消机制的网络请求库,需要手动取消请求。
        • 取消订阅:如取消对Redux store或者事件总线的订阅。
    • 注意事项
      • 忘记清理副作用可能会导致内存泄漏、性能下降或者其他意外行为。

二、React组件间的通信交互

1. 父子组件通信

  • 父组件向子组件传递数据(通过props
    • 方式:在父组件的render方法中,将数据作为属性传递给子组件。例如,父组件有一个数据message,传递给子组件ChildComponent
class ParentComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            message: 'Hello from parent'
        };
    }
    render() {
        return <ChildComponent message={this.state.message} />;
    }
}
class ChildComponent extends React.Component {
    render() {
        return <div>{this.props.message}</div>;
    }
}
  • 子组件向父组件通信(通过回调函数)
    • 方式:父组件将一个回调函数作为props传递给子组件,子组件在需要的时候调用这个回调函数,并将数据传递给父组件。例如,子组件中有一个按钮,点击按钮后将数据传递给父组件:
class ParentComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            childData: null
        };
    }
    handleChildData = (data) => {
        this.setState({ childData: data });
    };
    render() {
        return (
            <div>
                <ChildComponent sendDataToParent={this.handleChildData} />
                <div>{this.state.childData}</div>
            </div>
        );
    }
}
class ChildComponent extends React.Component {
    handleClick = () => {
        const data = 'Data from child';
        this.props.sendDataToParent(data);
    };
    render() {
        return <button onClick={this.handleClick}>Send Data to Parent</button>;
    }
}

2. 兄弟组件通信

  • 通过共同的父组件作为中间人
    • 方式:兄弟组件A和B,A要给B传递数据。A通过调用父组件传递过来的回调函数将数据传递给父组件,父组件再将数据通过props传递给B。例如:
class ParentComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            dataFromA: null
        };
    }
    handleDataFromA = (data) => {
        this.setState({ dataFromA: data });
    };
    render() {
        return (
            <div>
                <SiblingA sendDataToParent={this.handleDataFromA} />
                <SiblingB dataFromA={this.state.dataFromA} />
            </div>
        );
    }
}
class SiblingA extends React.Component {
    handleClick = () => {
        const data = 'Data from Sibling A';
        this.props.sendDataToParent(data);
    };
    render() {
        return <button onClick={this.handleClick}>Send Data</button>;
    }
}
class SiblingB extends React.Component {
        render() {
            return <div>{this.props.dataFromA}</div>;
        }
}
  • 使用状态管理库(如Redux、Mobx)
    • 方式
      • 以Redux为例,组件通过connect函数连接到Redux store,dispatch操作来触发状态改变,其他组件可以通过订阅store的状态更新来获取最新数据。
      • 首先定义action(描述发生的操作),例如:
const ADD_ITEM = 'ADD_ITEM';
export const addItem = (item) => ({
    type: ADD_ITEM,
    payload: item
});
    - 然后定义`reducer`(根据`action`更新状态),例如:
const initialState = {
    items: []
};
const itemReducer = (state = initialState, action) => {
    switch (action.type) {
        case ADD_ITEM:
            return {
               ...state,
                items: [...state.items, action.payload]
            };
        default:
            return state;
    }
};
    - 在组件中使用:
        - 发送数据的组件:
import React from 'react';
import { connect } from 'react-redux';
import { addItem } from './actions';
class ItemSender extends React.Component {
    handleSendItem = () => {
        const newItem = 'New Item';
        this.props.addItem(newItem);
    };
    render() {
        return <button onClick={this.handleSendItem}>Send Item</button>;
    }
}
const mapDispatchToProps = (dispatch) => ({
    addItem: (item) => dispatch(addItem(item))
});
export default connect(null, mapDispatchToProps)(ItemSender);
        - 接收数据的组件:
import React from 'react';
import { connect } from 'react-redux';
class ItemReceiver extends React.Component {
    render() {
        return (
            <div>
                {this.props.items.map((item, index) => (
                    <div key={index}>{item}</div>
                ))}
            </div>
        );
    }
}
const mapStateToProps = (state) => ({
    items: state.items
});
export default connect(mapStateToProps, null)(ItemReceiver);

三、React页面缓存机制应用

1. 使用React.memo进行组件缓存

  • 原理React.memo是一个高阶组件,它会对组件的props进行浅比较。如果props没有改变,组件就不会重新渲染,从而实现缓存效果。
  • 应用场景:对于纯展示组件,它们的渲染只依赖于props,并且重新渲染成本较高(如组件内部有复杂的计算或者渲染大量子元素)。例如:
const MyComponent = React.memo((props) => {
    console.log('MyComponent is rendering');
    return <div>{props.text}</div>;
});
  • 注意事项
    • 只是对props进行浅比较,对于复杂的数据结构(如嵌套对象或数组),可能会出现即使数据内容改变,但浅比较认为没有改变的情况。可以通过自定义比较函数来解决这个问题,React.memo可以接受第二个参数,一个比较函数。例如:
const areEqual = (prevProps, nextProps) => {
    return prevProps.text === nextProps.text;
};
const MyComponent = React.memo((props) => {
    console.log('MyComponent is rendering');
    return <div>{props.text}</div>;
}, areEqual);

2. 使用shouldComponentUpdate进行手动缓存控制

  • 原理:在组件内部实现shouldComponentUpdate生命周期方法,通过比较新的propsstate与旧的propsstate,来决定组件是否需要重新渲染。
  • 应用场景:当React.memo的浅比较不能满足需求,或者需要更精细地控制组件渲染时。例如,一个组件有多个props,但只有其中一个props的改变会影响渲染,就可以在shouldComponentUpdate中进行判断:
class MyComponent extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        return nextProps.importantValue!== this.props.importantValue;
    }
    render() {
        return <div>{this.props.importantValue}</div>;
    }
}
  • 注意事项
    • 需要谨慎实现,错误的比较逻辑可能导致组件不更新或者过度更新。

3. 使用第三方库(如react - keep - alive

  • 原理:这些库通常会在组件卸载时将组件的状态保存起来,当组件再次挂载时恢复状态,实现缓存效果。
  • 应用场景:在复杂的单页应用中,对于那些切换频繁但状态需要保留的组件很有用。例如,在一个多标签页的应用中,当切换离开某个标签页(组件卸载),再次切换回来时(组件挂载),组件的状态(如表单输入内容、滚动位置等)能够恢复。
  • 注意事项
    • 不同的第三方库有不同的使用方式和限制,需要仔细阅读文档。例如,有些库可能对组件的结构或者状态管理方式有特定的要求。

四、复杂商店应用(以Redux为例)

1. Redux核心概念

  • Store
    • 定义:它是一个单一的数据源,存储整个应用的状态。通过createStore函数(或者在一些高级配置中使用configureStore等)创建。例如:
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
  • 注意事项
    • 整个应用应该只有一个store,以保证状态的一致性。
    • store是不可变的,不能直接修改store中的状态,只能通过发送action来触发reducer更新状态。
  • Reducer
    • 定义:它是一个纯函数,用于根据action来更新store中的状态。它接收当前状态和一个action作为参数,并返回新的状态。例如:
const initialState = {
    count: 0
};
const counterReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return {
               ...state,
                count: state.count + 1
            };
        case 'DECREMENT':
            return {
               ...state,
                count: state.count - 1
            };
        default:
            return state;
    }
};
- **注意事项**:
    - 必须是纯函数,即对于相同的输入(当前状态和`action`),必须返回相同的输出(新的状态)。
    - 不能直接修改传入的`state`参数,应该返回一个新的状态对象,可以使用对象展开运算符(`...`)来创建新的对象。
  • Action
    • 定义:它是一个包含type属性的JavaScript对象,用于描述发生了什么操作。type属性通常是一个字符串常量,用于在reducer中识别不同的操作。例如:
const incrementAction = {
    type: 'INCREMENT'
};
  • 注意事项
    • type字段应该是唯一且具有描述性的,以便在reducer中能够准确地处理不同的操作。
    • 除了type字段,还可以包含其他数据(通过payload等字段)来传递操作所需的信息。

2. 在组件中使用Redux

  • 通过connect函数连接组件和store(在React - Redux库中)
    • 方式connect函数用于将组件与Redux的store连接起来,它接受两个参数:mapStateToPropsmapDispatchToProps
      • mapStateToProps用于将store中的状态映射为组件的props。例如:
const mapStateToProps = (state) => ({
    count: state.count
});
  • mapDispatchToProps用于将dispatch函数(用于触发action)映射为组件的props。例如:
const mapDispatchToProps = (dispatch) => ({
    increment: () => dispatch({ type: 'INCREMENT' }),
    decrement: () => dispatch({ type: 'DECREMENT' })
});
  • 注意事项
    - mapStateToPropsmapDispatchToProps都是可选的。如果组件只需要获取状态,只需要定义mapStateToProps

生命周期钩子函数 getDerivedStateFromProps 的使用场景

  1. constructor()
    • 这是挂载阶段首先执行的函数。它主要用于初始化组件的状态(this.state)和绑定事件处理函数。例如:
    class MyComponent extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                count: 0
            };
            // 绑定事件处理函数
            this.handleClick = this.handleClick.bind(this);
        }
        handleClick() {
            // 处理点击事件,更新状态等操作
            this.setState((prevState) => ({
                count: prevState.count + 1
            }));
        }
        //...
    }
    
  2. getDerivedStateFromProps()(如果定义)
    • 这个函数在constructor之后、render之前调用。它用于根据props的变化来更新state,返回一个对象来更新state,或者返回null表示不需要更新。例如:
    class MyComponent extends React.Component {
        static getDerivedStateFromProps(props, state) {
            if (props.someProp!== state.someProp) {
                return {
                    someProp: props.someProp
                };
            }
            return null;
        }
        //...
    }
    
  3. render()
    • getDerivedStateFromProps(如果有)之后执行。它是必需的方法,用于描述组件的UI结构,返回一个React元素(可以是原生DOM元素或者其他自定义组件)。例如:
    class MyComponent extends React.Component {
        //...
        render() {
            return (
                <div>
                    <p>Count: {this.state.count}</p>
                    <button onClick={this.handleClick}>Increment</button>
                </div>
            );
        }
    }
    
  4. componentDidMount()
    • 在组件挂载到DOM后立即执行。这个阶段适合进行一些需要DOM节点的操作,如发送网络请求获取数据填充组件内容、添加订阅、初始化第三方JavaScript库等。例如:
    class MyComponent extends React.Component {
        //...
        componentDidMount() {
            // 发送网络请求
            fetch('https://example.com/api/data')
              .then(response => response.json())
              .then(data => this.setState({data}));
        }
    }
    

所以,挂载阶段生命周期钩子函数的一般执行顺序是:constructor() -> getDerivedStateFromProps()(如果定义) -> render() -> componentDidMount()。需要注意的是,如果没有定义getDerivedStateFromProps,则直接从constructor跳到render

getDerivedStateFromPropscomponentDidMount的区别

  1. 执行时机
    • getDerivedStateFromProps:在组件实例化(通过构造函数constructor创建)之后、render方法之前调用,并且在组件每次接收到新的props时也会被调用。这意味着它在组件的初始挂载以及后续props更新时都会介入。
    • componentDidMount:在组件挂载到DOM之后才会被调用,也就是在render方法执行完成,且组件对应的真实DOM节点已经插入到文档(Document)之后。它只会在组件初始挂载时执行一次。
  2. 功能用途
    • getDerivedStateFromProps
      • 主要用于根据props的值来更新或派生组件的state。它是一个静态方法,不能访问组件实例(即不能使用this关键字)。例如,当props中的某个数据需要同步到state,以便在组件内部进行进一步处理或者在render方法中使用更新后的状态来生成UI时,就可以使用这个方法。
      class MyComponent extends React.Component {
          static getDerivedStateFromProps(props, state) {
              if (props.value!== state.value) {
                  return { value: props.value };
              }
              return null;
          }
          constructor(props) {
              super(props);
              this.state = { value: props.value };
          }
          //...
      }
      
      • 也用于在props变化时,对state进行有条件的更新,以确保stateprops之间的某种同步关系。
    • componentDidMount
      • 通常用于执行那些需要DOM节点已经存在才能进行的操作。例如,发送网络请求获取数据来填充组件内容。
      class DataFetcher extends React.Component {
          constructor(props) {
              super(props);
              this.state = { data: null };
          }
          componentDidMount() {
              fetch('https://example.com/api/data')
                .then(response => response.json())
                .then(data => this.setState({ data }));
          }
          //...
      }
      
      • 还用于初始化第三方JavaScript库,比如使用Chart.js在页面中绘制图表,或者添加事件监听器等操作,这些操作都依赖于组件已经挂载到DOM上。
  3. 使用限制和注意事项
    • getDerivedStateFromProps
      • 由于是静态方法,它不能直接访问组件实例的属性和方法。这意味着不能在其中调用this.setState来触发异步操作或者更新其他非派生自props的状态。
      • 必须返回一个对象用于更新state或者返回null表示不需要更新state。返回值会直接与当前state进行合并。
    • componentDidMount
      • 因为这个方法在组件挂载后执行,所以在其中进行的操作(如添加事件监听器、创建定时器等)需要在组件卸载时进行清理,以避免内存泄漏等问题。通常在componentWillUnmount方法中进行这些清理操作。

React Hook

  1. React Hook的种类

    • 基础Hook
      • useState:用于在函数组件中添加状态。它返回一个数组,其中第一个元素是状态值,第二个元素是更新状态的函数。
      • useEffect:用于处理函数组件中的副作用,如数据获取、订阅、手动修改DOM等。它可以模拟类组件中的生命周期方法,如componentDidMountcomponentDidUpdatecomponentWillUnmount
      • useContext:用于在函数组件中访问React Context。它使得组件能够订阅React应用中的上下文(Context),并且在上下文的值发生变化时重新渲染。
    • 额外的Hook(用于优化等场景)
      • useReducer:可以作为useState的替代方案,用于管理更复杂的状态逻辑。它接受一个reducer函数和一个初始状态作为参数,返回当前状态和一个dispatch函数,用于触发状态更新。
      • useCallback:用于优化性能,返回一个记忆化(memoized)的回调函数。只有在依赖项发生变化时,才会重新计算这个回调函数。
      • useMemo:用于缓存计算结果,只有在依赖项改变时才会重新计算返回值,避免在每次组件重新渲染时都进行昂贵的计算。
      • useRef:返回一个可变的ref对象,其.current属性被初始化为传入的参数。可以用于访问DOM元素或者在组件的多次渲染之间保存一个可变的值。
      • useImperativeHandle:用于在使用ref时,自定义暴露给父组件的实例值。通常与forwardRef一起使用,来控制组件内部实例的哪些属性或方法可以被外部访问。
      • useLayoutEffect:与useEffect类似,但它会在所有的DOM变更之后同步调用,在浏览器进行绘制之前。可以用于读取DOM布局并同步触发重绘。
  2. 开发中常用的React Hook及其应用场景

    • useState
      • 应用场景:用于在函数组件中存储和更新简单的状态。例如,创建一个计数器组件,或者存储表单输入框中的值。
      • 示例代码
      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>
          );
      };
      
    • useEffect
      • 应用场景
        • 数据获取:在组件挂载时获取数据,如从API获取用户列表、文章列表等。
        • 事件订阅和取消订阅:订阅外部事件源(如窗口滚动事件、自定义事件等),并在组件卸载时取消订阅,避免内存泄漏。
        • 操作DOM(结合useRef:对DOM元素进行操作,如聚焦输入框、获取元素尺寸等。
      • 示例代码(数据获取)
      import React, { useEffect, useState } from 'react';
      const UserList = () => {
          const [users, setUsers] = useState([]);
          useEffect(() => {
              fetch('https://example.com/api/users')
             .then(response => response.json())
             .then(data => setUsers(data));
          }, []);
          return (
              <ul>
                  {users.map(user => (
                      <li key={user.id}>{user.name}</li>
                  ))}
              </ul>
          );
      };
      
    • useContext
      • 应用场景:用于在组件树中共享数据,避免层层传递props。例如,在一个主题切换应用中,共享主题状态(如亮色主题或暗色主题),使得多个组件能够根据主题状态进行渲染。
      • 示例代码
      import React, { createContext, useContext, useState } from 'react';
      const ThemeContext = createContext();
      const ThemeProvider = ({ children }) => {
          const [theme, setTheme] = useState('light');
          return (
              <ThemeContext.Provider value={{ theme, setTheme }}>
                  {children}
              </ThemeContext.Provider>
          );
      };
      const Button = () => {
          const { theme, setTheme } = useContext(ThemeContext);
          const toggleTheme = () => {
              setTheme(theme === 'light'? 'dark' : 'light');
          };
          return (
              <button onClick={toggleTheme}>
                  {theme === 'light'? 'Switch to Dark Theme' : 'Switch to Light Theme'}
              </button>
          );
      };
      const App = () => {
          return (
              <ThemeProvider>
                  <Button />
              </ThemeProvider>
          );
      };
      
    • useReducer
      • 应用场景:当组件的状态更新逻辑比较复杂,涉及多个子状态或者有复杂的状态转换规则时使用。例如,在一个购物车组件中,管理购物车中商品的添加、删除、数量修改等操作,状态更新逻辑可以通过reducer函数来统一管理。
      • 示例代码
      import React, { useReducer } from 'react';
      const initialState = {
          cart: []
      };
      const reducer = (state, action) => {
          switch (action.type) {
              case 'ADD_TO_CART':
                  return {
                     ...state,
                      cart: [...state.cart, action.payload]
                  };
              case 'REMOVE_FROM_CART':
                  return {
                     ...state,
                      cart: state.cart.filter(item => item.id!== action.payload.id)
                  };
              default:
                  return state;
          }
      };
      const ShoppingCart = () => {
          const [state, dispatch] = useReducer(reducer, initialState);
          const addToCart = (product) => {
              dispatch({ type: 'ADD_TO_CART', payload: product });
          };
          const removeFromCart = (product) => {
              dispatch({ type: 'REMOVE_FROM_CART', payload: product });
          };
          return (
              <div>
                  <button onClick={() => addToCart({ id: 1, name: 'Product 1' })}>Add to Cart</button>
                  <ul>
                      {state.cart.map(item => (
                          <li key={item.id}>
                              {item.name}
                              <button onClick={() => removeFromCart(item)}>Remove</button>
                          </li>
                      ))}
                  </ul>
              </div>
          );
      };
      
    • useCallback
      • 应用场景:用于优化性能,当把一个回调函数作为props传递给子组件,并且这个回调函数在组件的多次渲染过程中不应该被重新创建(除非其依赖项发生变化)时使用。例如,在一个包含大量子组件的列表中,父组件有一个删除按钮的回调函数,使用useCallback可以避免不必要的子组件重新渲染。
      • 示例代码
      import React, { useState, useCallback } from 'react';
      const ParentComponent = () => {
          const [count, setCount] = useState(0);
          const handleDelete = useCallback(() => {
              // 执行删除操作
              console.log('Delete item');
          }, []);
          return (
              <div>
                  <p>Count: {count}</p>
                  <button onClick={() => setCount(count + 1)}>Increment</button>
                  <ChildComponent onDelete={handleDelete} />
              </div>
          );
      };
      const ChildComponent = ({ onDelete }) => {
          return (
              <button onClick={onDelete}>Delete</button>
          );
      };
      
    • useMemo
      • 应用场景:用于缓存计算结果,当组件中有一些昂贵的计算(如复杂的数据转换、大量数据的过滤等),并且这些计算结果在依赖项没有改变的情况下不需要重新计算时使用。例如,在一个数据表格组件中,对表格数据进行排序和过滤的计算可以使用useMemo进行缓存。
      • 示例代码
      import React, { useState, useMemo } from 'react';
      const DataTable = () => {
          const [data, setData] = useState([
              { id: 1, name: 'John', age: 30 },
              { id: 2, name: 'Alice', age: 25 },
              { id: 3, name: 'Bob', age: 35 }
          ]);
          const [sortBy, setSortBy] = useState('name');
          const sortedData = useMemo(() => {
              if (sortBy === 'name') {
                  return data.sort((a, b) => a.name.localeCompare(b.name));
              } else if (sortBy === 'age') {
                  return data.sort((a, b) => a.age - b.age);
              }
              return data;
          }, [data, sortBy]);
          return (
              <div>
                  <select onChange={(e) => setSortBy(e.target.value)}>
                      <option value="name">Sort by Name</option>
                      <option value="age">Sort by Age</option>
                  </select>
                  <table>
                      <thead>
                          <tr>
                              <th>ID</th>
                              <th>Name</th>
                              <th>Age</th>
                          </tr>
                      </thead>
                      <tbody>
                          {sortedData.map(item => (
                              <tr key={item.id}>
                                  <td>{item.id}</td>
                                  <td>{item.name}</td>
                                  <td>{item.age}</td>
                              </tr>
                          ))}
                      </tbody>
                  </table>
              </div>
          );
      };
      

useState 和 useReducer 的区别

  1. 状态管理的复杂度
    • useState
      • 简单状态管理useState主要用于管理简单的、独立的状态。例如,一个计数器组件中的计数状态,或者一个输入框组件中的输入值状态。
      • 示例代码(计数器)
      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>
          );
      };
      
    • useReducer
      • 复杂状态管理useReducer适用于处理更复杂的状态逻辑,特别是当状态的更新依赖于前一个状态,并且涉及多种不同的操作类型时。例如,在一个表单组件中,管理表单的提交状态、验证状态以及字段值状态等多个相关状态。
      • 示例代码(简单的表单验证)
      import React, { useReducer } from 'react';
      const initialState = {
          value: '',
          isTouched: false,
          isError: false
      };
      const reducer = (state, action) => {
          switch (action.type) {
              case 'INPUT_CHANGE':
                  return {
                    ...state,
                      value: action.payload,
                      isError: false
                  };
              case 'INPUT_BLUR':
                  return {
                    ...state,
                      isTouched: true,
                      isError: state.value.trim() === ''
                  };
              default:
                  return state;
          }
      };
      const InputForm = () => {
          const [state, dispatch] = useReducer(reducer, initialState);
          const { value, isTouched, isError } = state;
          const onChangeHandler = (e) => {
              dispatch({ type: 'INPUT_CHANGE', payload: e.target.value });
          };
          const onBlurHandler = () => {
              dispatch({ type: 'INPUT_BLUR' });
          };
          return (
              <div>
                  <input
                      type="text"
                      value={value}
                      onChange={onChangeHandler}
                      onBlur={onBlurHandler}
                  />
                  {isTouched && isError && <p>Input must not be empty.</p>}
              </div>
          );
      };
      
  2. 状态更新方式
    • useState
      • 直接更新:通过调用setState函数(useState返回的第二个元素)来更新状态。这个函数可以接收新的状态值作为参数,React会自动将新值与旧值进行合并(对于对象类型的状态),或者直接替换(对于基本类型的状态)。
      • 示例(更新对象状态)
      import React, { useState } from 'react';
      const UserProfile = () => {
          const [user, setUser] = useState({
              name: 'John',
              age: 30
          });
          const updateName = () => {
              setUser({
                ...user,
                  name: 'Alice'
              });
          };
          return (
              <div>
                  <p>Name: {user.name}</p>
                  <p>Age: {user.age}</p>
                  <button onClick={updateName}>Update Name</button>
              </div>
          );
      };
      
    • useReducer
      • 基于动作(Action)更新useReducer返回一个包含当前状态和dispatch函数的数组。通过dispatch函数发送一个动作(action)来触发状态更新。action是一个包含type属性(通常是一个字符串,用于标识动作类型)和可选的payload属性(用于传递与动作相关的数据)的对象。reducer函数根据接收到的action类型来决定如何更新状态。
      • 示例(计数器的另一种实现方式)
      import React, { useReducer } from 'react';
      const initialState = 0;
      const reducer = (state, action) => {
          switch (action.type) {
              case 'INCREMENT':
                  return state + 1;
              case 'DECREMENT':
                  return state - 1;
              default:
                  return state;
          }
      };
      const Counter = () => {
          const [count, dispatch] = useReducer(reducer, initialState);
          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>
          );
      };
      
  3. 可预测性和调试便利性
    • useState
      • 简单但可能复杂的更新逻辑:对于简单的状态更新,useState很直观。但当状态更新逻辑变得复杂,涉及多个依赖于旧状态的操作时,可能会导致代码难以理解和调试。例如,在一个复杂的异步操作后更新多个状态时,很难追踪每个状态更新的顺序和原因。
    • useReducer
      • 更具可预测性的更新流程useReducer的更新逻辑基于reducer函数,它是一个纯函数,对于给定的当前状态和action,总是返回相同的新状态。这种确定性使得状态更新的流程更加清晰,便于调试。在大型应用或复杂的组件中,当状态更新依赖于多种条件和操作时,useReducer有助于保持代码的可维护性和可预测性。

总结 (react 主要生命周期,常用生命周期,不常用生命周期,特殊环境下的生命周期)

  1. 主要生命周期(函数组件和类组件都涉及或有替代方式)

    • 挂载(Mounting)阶段
      • constructor(类组件):用于初始化组件的状态(this.state)和绑定事件处理函数。在组件创建时首先被调用,必须先调用super(props)
      • render:在挂载和更新阶段都会被调用,是React组件中唯一必需的方法。用于描述组件的UI结构,返回一个React元素,应该是一个纯函数。
      • componentDidMount(类组件)/ useEffect(函数组件):在组件挂载到DOM后立即被调用(useEffect可以通过空依赖数组模拟)。适合进行需要DOM节点的操作,如发送网络请求、初始化第三方库等。
    • 更新(Updating)阶段
      • shouldComponentUpdate(类组件)/ React.memo(函数组件):用于性能优化,决定组件是否需要重新渲染。shouldComponentUpdate在类组件接收新的props或者state更新之前被调用,React.memo是一个高阶组件,用于对函数组件的props进行浅比较来决定是否重新渲染。
      • render(同挂载阶段):更新阶段也会调用render来生成新的虚拟DOM。
      • componentDidUpdate(类组件)/ useEffect(函数组件):在组件更新后被调用。可以在这里操作DOM,根据更新前后的propsstate进行一些额外的操作,useEffect通过依赖数组来控制在特定状态或属性变化时执行副作用。
    • 卸载(Unmounting)阶段
      • componentWillUnmount(类组件)/ useEffect(函数组件返回清理函数):在组件从DOM中移除之前被调用。用于清理在componentDidMount或其他生命周期中创建的副作用,如清除定时器、取消网络请求、取消订阅等。
  2. 常用生命周期(主要是在类组件中)

    • componentDidMount
      • 应用场景广泛,几乎所有需要在组件加载后进行的操作都会用到。例如,在组件挂载后发送网络请求获取数据来填充组件内容。
      class DataFetcher extends React.Component {
          constructor(props) {
              super(props);
              this.state = { data: null };
          }
          componentDidMount() {
              fetch('https://example.com/api/data')
               .then(response => response.json())
               .then(data => this.setState({ data }));
          }
          render() {
              return (
                  <div>
                      {this.state.data? (
                          <ul>
                              {this.state.data.map(item => (
                                  <li key={item.id}>{item.name}</li>
                              ))}
                          </ul>
                      ) : (
                          <p>Loading...</p>
                      )}
                  </div>
              );
          }
      }
      
    • shouldComponentUpdate
      • 用于性能优化,当组件重新渲染成本较高或者需要避免不必要的渲染时使用。例如,一个组件只有在特定props变化时才需要重新渲染。
      class MyComponent extends React.Component {
          shouldComponentUpdate(nextProps, nextState) {
              return nextProps.importantValue!== this.props.importantValue;
          }
          render() {
              return <div>{this.props.importantValue}</div>;
          }
      }
      
    • componentDidUpdate
      • 当组件更新后需要进行一些额外操作时使用。例如,根据组件更新后的状态来更新第三方库的配置。
      class ChartComponent extends React.Component {
          constructor(props) {
              super(props);
              this.state = { data: [] };
          }
          componentDidMount() {
              // 初始数据加载和图表初始化
              this.fetchDataAndUpdateChart();
          }
          componentDidUpdate(prevProps, prevState) {
              if (prevState.data!== this.state.data) {
                  // 数据变化后更新图表
                  this.updateChartWithNewData();
              }
          }
          fetchDataAndUpdateChart = () => {
              fetch('https://example.com/api/chart-data')
               .then(response => response.json())
               .then(data => this.setState({ data }));
          };
          updateChartWithNewData = () => {
              // 使用this.state.data更新图表的逻辑
          };
          render() {
              return <div id="chart-container"></div>;
          }
      }
      
  3. 不常用生命周期(类组件)

    • componentWillMount(已废弃)
      • 因为可能会导致一些难以预测的副作用,并且在异步渲染等场景下可能会出现问题,在React 16.3版本后被标记为不安全的生命周期方法,不建议使用。它在组件挂载之前被调用,且在render方法之前。
    • componentWillReceiveProps(已废弃)
      • 会导致性能问题和意外的行为,尤其是在父组件频繁更新传递props的情况下。从React 16.3版本后被标记为不安全的生命周期方法,不建议使用。它在组件接收到新的props时被调用,在render方法之前。
    • componentWillUpdate(已废弃)
      • 同样可能导致难以预测的副作用,在异步渲染场景下会出现问题。React 16.3版本后被标记为不安全的生命周期方法,不建议使用。在组件更新之前被调用,在shouldComponentUpdate之后,render之前。
  4. 特殊环境下的生命周期(类组件)

    • getDerivedStateFromProps
      • 应用场景包括根据props初始化或更新state,实现受控组件和非受控组件之间的转换等。它是一个静态方法,在组件实例化之后、render方法之前调用,并且在组件每次接收到新的props时也会被调用。
      class InputWrapper extends React.Component {
          static getDerivedStateFromProps(props, state) {
              if ('value' in props) {
                  return {
                      isControlled: true,
                      value: props.value
                  };
              }
              return {
                  isControlled: false,
                  value: state.value
              };
          }
          constructor(props) {
              super(props);
              this.state = {
                  value: '',
                  isControlled: false
              };
          }
          handleChange = (e) => {
              if (!this.state.isControlled) {
                  this.setState({
                      value: e.target.value
                  });
              }
          };
          render() {
              return (
                  <input
                      type="text"
                      value={this.state.isControlled? this.state.value : undefined}
                      onChange={this.handleChange}
                  />
              );
          }
      }
      
    • getSnapshotBeforeUpdate
      • 在组件更新阶段被调用,具体是在render方法之后,componentDidUpdate之前。用于获取组件更新前的最后一个“快照”(如DOM状态或其他相关信息),并将其传递给componentDidUpdate。例如,用于保存滚动位置或记录元素尺寸变化等场景。
      class ScrollableList extends React.Component {
          constructor(props) {
              super(props);
              this.listRef = React.createRef();
              this.state = {
                  items: [],
                  scrollTop: 0
              };
          }
          componentDidMount() {
              // 模拟获取初始数据
              this.setState({
                  items: [1, 2, 3, 4, 5]
              });
          }
          handleAddItem = () => {
              const newItems = [...this.state.items, this.state.items.length + 1];
              this.setState({
                  items: newItems
              });
          }
          getSnapshotBeforeUpdate(prevProps, prevState) {
              if (this.listRef.current) {
                  return this.listRef.current.scrollTop;
              }
              return null;
          }
          componentDidUpdate(prevProps, prevState, snapshot) {
              if (snapshot!== null) {
                  this.listRef.current.scrollTop = snapshot;
              }
          }
          render() {
              return (
                  <div ref={this.listRef} style={{ height: '200px', overflow: 'auto' }}>
                      <ul>
                          {this.state.items.map((item) => (
                              <li key={item}>{item}</li>
                          ))}
                      </ul>
                      <button onClick={this.handleAddItem}>Add Item</button>
                  </div>
              );
          }
      }
      
      在这里插入图片描述

标签:React,return,复习,react,state,加深,props,组件,const
From: https://blog.csdn.net/m0_51244077/article/details/143245867

相关文章

  • React项目搭建
    1.环境准备确保你的计算机上已安装以下工具:Node.js:React需要Node.js来运行和管理依赖。你可以从Node.js官网下载并安装最新版本。npm:Node.js安装后会自带npm(NodePackageManager),用于管理项目依赖。2.创建项目使用CreateReactApp是快速创建React应用的推荐方式。打开终......
  • [计划] CSP-S2 2024 考前复习
    怎么算空间???复习板子floydcrtecgcd单调队列prim(kruskal求最小生成树)并查集各种写法、复杂度区间加区间和BITBIT注意位置是否会到0FHQ-TreapFHQ-Treap勿把Split_Val和Split_Siz写混;FHQ-Treap记得Split时PushUp注意FHQ-Treap初值问题字符串哈希区间......
  • Java复习16(PTA)
    快递计价器分数20全屏浏览切换布局作者 大数据2021单位 山东科技大学现需要编写一个简易快递计价程序。具体来说:1、抽象快递类Express,其包含一个属性intweight表示快递重量(单位为kg),一个方法getWeight()用于返回快递重量和一个抽象方法getTotal()用于计算快递运费......
  • csp-s复习
    字符串triekmpacminemanacherdp看起来能dp的就胡个dp上去1.优化状态(大概率会使用贪心)2.套公式(前缀和/单调队列...)3.找性质,有哪些是无用的[P11218]trick排列计数排列的状态很难压进去,所以我们考虑如何将这个去掉。我们的dp方程要用最少的空间来转移,也就是把状态压......
  • 复习
    发出来给大伙参考吧数学逆元递推式(需要满足模数为质数)inv[1]=1;inv[i]=(p-p/i)*inv[p%i]%p;DP\[f_i=\max_{j\lti}f_j\]\[f_{i,j}=\max_{k\ltj}(f_{i,k}+cost)\]单调队列/对每个\(i\)维护单调队列二维线性DP存在一个同时由\(j,k\)影响的变量,则该状态转移方......
  • MySQL 复习(一):建表约束
    MySQL复习(一):建表约束@目录MySQL复习(一):建表约束1.主键约束1.1添加主键约束1.1.1建表前添加主键约束1.1.2建表后添加主键约束1.2删除主键约束2.外键约束2.1添加外键约束2.1.1建表前添加外键约束2.1.2建表后添加外键约束2.2删除外键约束3.自增约束3.1......
  • ReactOS系统中平衡二叉树,在一个空间中寻找与给定地址范围重合或部分重合的(已分配)区间
    在一个空间中寻找与给定地址范围重合或部分重合的(已分配)区间PMEMORY_AREANTAPIMmLocateMemoryAreaByRegion(PMADDRESS_SPACEAddressSpace,PVOIDAddress,ULONG_PTRLength);MmLocateMemoryAreaByRegion/******************************************************......
  • ReactOS系统中平衡二叉树。给定地址超导其所属区块MmFindRegion()
    系列文章目录PMM_REGIONNTAPIMmFindRegion(PVOIDBaseAddress,PLIST_ENTRYRegionListHead,PVOIDAddress,PVOID*RegionBaseAddress);宏函数//给定地址找到其中所属区块#defineCONTAINING_RECORD(address,type,field)((typeFAR*\(PCHAR)(address)-(PCH......
  • [复习] 数论基础
    [复习]数论基础模运算\[(a\pmb)\bmodp=((a\bmodp)\pm(b\bmodp))\bmodp\]\[(a\timesb)\bmodp=((a\bmodp)\times(b\bmodp))\bmodp\]积性函数\[\forall\gcd(x,y)=1,f(xy)=f(x)\timesf(y)\]完全积性函数\[\forallx,y\inN^+,f(xy)=f(x)\timesf(y)\]g......
  • 挑战中,Java面试题复习第5天,坚持就是胜利。
     ......