React组件和生命周期

72 阅读5分钟

1. 组件

1.1 组件有函数式组件和Class类组件

// 函数组件
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 类组件
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

1.2 渲染组件

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.createRoot(
  document.getElementById('root')
).render(element);

// 自定义组件使用大写字母开头
import React from 'react';

// 正确!组件需要以大写字母开头:
function Hello(props) {
  // 正确! 这种 <div> 的使用是合法的,因为 div 是一个有效的 HTML 标签:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 正确!React 知道 <Hello /> 是一个组件,因为它是大写字母开头的:
  return <Hello toWhat="World" />;
}

1.3 受控组件 与 非受控组件

受控组件: 对某个组件状态的掌控,它的值是否只能由用户设置,而不能通过代码控制;

在HTML的表单元素中,他们通常自己维护一套state,并随着用户的输入自己进行UI上的更新,这种行为是不被程序所管控的。而如果将React里的state属性和表单元素的值建立依赖关系,再通过onChange事件与setState()结合更新state属性,就能达到控制用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件。

// input自身维护的状态,外界无法获取数据
class TestComponent extends React.Component {
  render () {
    return <input name="username" />
  }
}

// 可以设置初始值
class TestComponent extends React.Component {
  constructor (props) {
    super(props);
    this.state = { username: 'test' };
  }
  render () {
    return <input name="username" value={this.state.username} />
  }
}

// 可以读取并设置初始值
class TestComponent extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      username: "test"
    }
  }
  onChange (e) {
    console.log(e.target.value);
    this.setState({
      username: e.target.value
    })
  }
  render () {
    return <input name="username" value={this.state.username} onChange={(e) => this.onChange(e)} />
  }

非受控组件: 组件内的状态不由用户控制;

// 如果不想关心表单元素的值是如何变化的,只想取值,可以使用ref
import React, { Component } from 'react';

export class UnControll extends Component {
  constructor (props) {
    super(props);
    this.inputRef = React.createRef();
  }
  handleSubmit = (e) => {
    console.log('我们可以获得input内的值为', this.inputRef.current.value);
    e.preventDefault();
  }
  render () {
    return (
      <form onSubmit={e => this.handleSubmit(e)}>
        <input defaultValue="lindaidai" ref={this.inputRef} />
        <input type="submit" value="提交" />
      </form>
    )
  }
}

1.4 props

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

// 错误,要像纯函数一样幂等
function withdraw(account, amount) {
  account.total -= amount;
}

1.5 state

// 使用props形式
function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

// 如何避免多次React.DOM render?

// 引用生命周期,根组件保留一个
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
  1. setState
// 构造函数是唯一可以给state赋值的地方
this.setState({comment: 'Hello'});
  1. state更新可能是异步的
// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});
// Correct
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});
  1. state更新会合并
constructor(props) {
  super(props);
  this.state = {
    posts: [],
    comments: []
  };
}

componentDidMount() {
  fetchPosts().then(response => {
    // 相当于{post: response.posts, ...otherState}
    this.setState({
      posts: response.posts
    });
  });

  fetchComments().then(response => {
    this.setState({
      comments: response.comments
    });
  });
}
  1. 单向数据流 state 只在当前的组件里生效,属于组件内的属性,重复实例化相同的组件,内部的内存地址也是不一样的; 例如Clock中计时器都是独立的

setState 异步

异步目的:batch 处理,性能优化

  1. 合成事件
class App extends Component {
	
	state = { val: 0 }
	
	increment = () => {
		this.setState({ val: this.state.val + 1 })
		console.log(this.state.val) // 输出的是更新前的val --> 0
	}
	
	render() {
		return (
			<div onClick={this.increment}>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}
  1. 生命周期
class App extends Component {
	
	state = { val: 0 }
	
	componentDidMount() {
		this.setState({ val: this.state.val + 1 })
		console.log(this.state.val) // 输出的还是更新前的值 --> 0
	}
	render() {
		return (
			<div>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}
  1. 原生事件
class App extends Component {
	
	state = { val: 0 }
	
	changeValue = () => {
		this.setState({ val: this.state.val + 1 })
		console.log(this.state.val) // 输出的是更新后的值 --> 1
	}
	
	componentDidMount() {
		document.body.addEventListener('click', this.changeValue, false)
	}
	
	render() {
		return (
			<div>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}
  1. setTimeout
class App extends Component {
	
	state = { val: 0 }
	
	componentDidMount() {
		setTimeout(_ => {
			this.setState({ val: this.state.val + 1 })
			console.log(this.state.val) // 输出更新后的值 --> 1
		}, 0)
	}
	
	render() {
		return (
			<div>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}
  1. 批处理
class App extends Component {
	
	state = { val: 0 }
	
	batchUpdates = () => {
		this.setState({ val: this.state.val + 1 })
		this.setState({ val: this.state.val + 1 })
		this.setState({ val: this.state.val + 1 })
	}
	
	render() {
		return (
			<div onClick={this.batchUpdates}>
				{`Counter is ${this.state.val}`} // 1
			</div>
		)
	}
}
  1. setState 只在合成事件和生命周期是“异步”的,在原生事件和 setTimeout 中都是同步的;
  2. setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

2. 生命周期

在这里只总结一些常用的生命周期函数。

2.1 render

它是class组件必需的方法,获取最新的 props 和 state,在不修改组件 state 的情况下,每次调用时都返回相同的结果。

2.2 constructor

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

  • 通过给 this.state 赋值对象来初始化内部 state。
  • 为事件处理函数绑定实例。
constructor(props) {
  super(props);
  // 不要在这里调用 this.setState()
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}

1. 不要调用 setState()
2. 避免将 props 的值复制给 state
this.state = { color: props.color }; // wrong

2.3 componentDidMount

会在组件挂载后(插入DOM树中)立即调用。依赖于 DOM 节点的初始化应该放在这里,如需通过网络请求获取数据。可以在此声明周期里加 setState,但发生在浏览器更新屏幕之前,会导致性能问题;

有更新在render阶段的 constructor 中 init State,但有更新可以在此方法时 setState。

2.4 componentDidUpdate

componentDidUpdate(prevProps, prevState, snapshot)

会在更新后被立即调用。首次渲染不会执行此方法。

componentDidUpdate(prevProps) {
  // 典型用法(不要忘记比较 props):加条件判断,不然死循环
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}
// 如果组件实现了 getSnapshotBeforeUpdate() 生命周期,
// 则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。

如果 shouldComponentUpdate() 返回值 为 false,则不会调用componentDidUpdate()。

2.5 componentWillUnmount

componentWillUnmount()会在组件卸载及销毁之前直接调用。例如,清除timer,取消网络请求。

componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。