四、state与生命周期

1、什么是state

state 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。 属性 props 是外界传递过来的,状态 state 是组件本身的,状态可以在组件中任意修改。组件的属性和状态改变都会更新视图。

2、react组件的生命周期

react学习日志3

 

每个组件都包含“生命周期方法”,你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。

1)挂载

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下

 

constructor()

constructor(props)
  • 如果不初始化state或不进行方法绑定,则不需要为组件实现构造函数。
  • 只要使用了constructor()就必须写super(),否则会导致this指向错误。

构造函数仅用于以下两种情况:通过给 this.state 赋值对象来初始化内部 state。为事件处理函数绑定实例。在 constructor() 函数中不要调用 setState() 方法。

constructor(props) {
  // 必须写super(props) 
  super(props);
  // 不要在这里调用 this.setState()
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}

避免将props的值复制给state!这是一个常见错误!

constructor(props) {
 super(props);
 // 不要这样做
 this.state = { color: props.color };
}

static getDerivedStateFromProps()

static getDerivedStateFromProps(props, state)
  • 代替componentWillReceiveProps()
  • getDerivedStateFromProps 会在调用 render 方法之前调用并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
  • getDerivedStateFromProps 的存在只有一个目的:让组件在 props 变化时更新 state。
  • componentWillReceiveProps()与getDerivedStateFromProps()比较:

可实现:一是根据 props 来更新 state,二是触发一些回调,如动画或页面跳转

// before
// 判断前后props是否相同,如果不同将新的props更新到相应的state上去
// 会破坏 state 数据的单一数据源,导致组件状态变得不可预测
// 会增加组件的重绘次数
componentWillReceiveProps(nextProps) {
  if (nextProps.isLogin !== this.props.isLogin) {
    this.setState({ 
      isLogin: nextProps.isLogin,   
    });
  }
  if (nextProps.isLogin) {
    this.handleClose();
  }
}


// after
static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.isLogin !== prevState.isLogin) {
    return {
      isLogin: nextProps.isLogin,
    };
  }
  return null;
}

componentDidUpdate(prevProps, prevState) {
  if (!prevState.isLogin && this.props.isLogin) {
    this.handleClose();
  }
}

render()

  • render() 方法是 class 组件中唯一必须实现的方法。
  • render被调用时会检查this.props和this.state的变化,并返回React元素/数组或fragments/portals/字符串或数值/布尔类型或null
  • render()应为纯函数,即在不修改state的情况下每次调用都返回相同结果也不直接与浏览器交互。如要与浏览器进行交互可在componentDidMount()或其他生命周期中进行。
  • 如果shouldComponentUpdate()返回false 则不会调用render()

 

componentWillMount() (即将过时

componentWillMount()一般用的比较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时

 

componentDidMount()

在组件挂载后(插入DOM树中)立即调用。依赖于DOM节点的初始化应放这里,如请求接口。也适合在此处添加订阅,不要忘记在componentWillUnmount() 里取消订阅 。

在这里可以直接调用setState(),将触发额外渲染(发送在浏览器更新屏幕前,从而保证render调用两次用户也看不到中间状态)

 

2)更新

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

static getDerivedStateFromProps()

看上文。

 

shouldComponentUpdate()

shouldComponentUpdate(nextProps, nextState)
  • 根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。
  • 首次渲染或使用 forceUpdate() 时不会调用该方法。
  • 此方法仅作为性能优化的方式而存在

 

render()

看上文。

 

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate(prevProps, prevState)

代替componentWillUpdate。

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 我们是否在 list 中添加新的 items ?
    // 捕获滚动​​位置以便我们稍后调整滚动位置。
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
    // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
    //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.listRef}>{/* ...contents... */}</div>
    );
  }
}

componentDidUpdate()

componentDidUpdate(prevProps, prevState, snapshot)

在更新后立即被调用,可以在此处进行DOM操作,也可以比较前后props进行网络请求。

componentDidUpdate(prevProps) {
  // 典型用法(不要忘记比较 props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}
  • 在此处可以直接调用setState(),但必须写在条件语句里面否则会导致死循环和额外的重新渲染
  • 如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用),则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
  • 如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()。

 

componentWillUpdate() (即将过时

componentWillReceiveProps() (即将过时

 

3)卸载

当组件从 DOM 中移除时会调用如下方法:

componentWillUnmount()

  • componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等
  • componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

 

4)错误处理

当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

static getDerivedStateFromError()

static getDerivedStateFromError(error)

此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。在渲染阶段调用。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显降级 UI
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级  UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children; 
 }
}

componentDidCatch()

componentDidCatch(error, info)

生命周期在后代组件抛出错误后被调用

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显示降级 UI
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // "组件堆栈" 例子:
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    logComponentStackToMyService(info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级 UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children; 
}
}

3、在class组件中使用state与生命周期方法

// 计时器
// function tick() {
//   const element = (
//     <div>it is { new Date().toLocaleTimeString()}</div>
//   )
//   ReactDOM.render(element, document.getElementById('root'))
// }
// setInterval(tick, 1000)

// 修改计时器
class Clock extends React.Component {
  // 构造函数 
  constructor(props) {
    super(props); // 将props传递到父类的构造函数中
    this.state = {
      date: new Date()
    }
  }
  // 生命周期--挂载
  componentDidMount() {
    this.timerId = setInterval(() => this.tick(), 1000)
  }
  // 生命周期--卸载
  componentWillUnmount() {
    clearInterval(this.timerId)
  }
  // 更新state 
  tick() {
    this.setState({
      date: new Date()
    })
  }
  render() {
    return <div>it is {this.state.date.toLocaleTimeString()}</div>
  }
}
ReactDOM.render(<Clock />, document.getElementById('root')

4、正确使用state

1)不能直接修改state

// Wrong
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});

构造函数是唯一可以给 this.state 赋值的地方

2)state的更新可能是异步的

因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。

 // Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});
// Correct
// 上一个state作为第一个参数 更新被应用的props作为第二个参数
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

3)state的更新会被合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。

constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }
  // 你可以分别调用 setState() 来单独地更新它们
  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });

这里的合并是浅合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替换了 this.state.comments。

 

5、数据是向下流动的

不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,是函数组件还是 class 组件。除了拥有并设置了状态的组件,其他组件都无法访问。

组件可以选择把它的 state 作为 props 向下传递到它的子组件中

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

在自定义组件同样适用

<FormattedDate date={this.state.date} />


// FormattedDate 组件会在其 props 中接收参数 date,但是组件本身无法知道它是来自于 Clock 的 state,或是 Clock 的 props,还是手动输入的
function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件

如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动

 

其他

3、函数组件与class组件区别

a.函数组件的性能比class组件性能更高(类组件使用需实例化而函数组件直接返回结果,为提高性能尽量使用函数组件

b.函数组件没有this、没有生命周期、没有状态state;

类组件有this、有生命周期、有状态state

相关文章:

  • 2021-12-17
  • 2021-06-19
  • 2021-07-27
  • 2022-12-23
  • 2021-07-10
  • 2021-06-02
  • 2021-12-20
  • 2021-10-16
猜你喜欢
  • 2021-04-17
  • 2021-08-17
  • 2021-05-14
  • 2021-10-21
  • 2021-10-06
  • 2021-10-22
相关资源
相似解决方案