React-class组件和各生命周期

298 阅读6分钟

创建class组件

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {  }
    }
    render() { 
        return (  );
    }
}
 
export default App;

外部数据props

读this.props

上面的代码写了组件接收props,如果要使用,则可以直接写this.props来读,实际上不在constructor内接收参数props,貌似也不报错。

react官方不建议修改props,它所倡导的思想是此数据应该由它的主人来修改,以避免子组件改动带来的混乱。

props的作用

  • 接收外部数据,只能读,写应当由父组件设置写的函数
  • 接收外部函数,可以接收父组件修改props的函数

相关钩子componentWillReceiveProps

这个api已经被弃用了,它的新名字是UNSAFE_componentWillReceiveProps

它的参数是 (nextProps)

它的作用是当props修改之前,会执行这个函数,那么新的props会当做它的参数传递进来,如果想要使用旧数据,那么就调用this.props,如果想要使用新数据,那么就调用nextProps

state内部数据

在class组件中,使用

this.state={
	n:xxx,
    m:{xx:xxx}
}

来初始化state。

读-this.state

直接使用this.state.xxx.yyy等形式读就可以了,没什么好说的

写-setState

setState(updater[,callback])

第一个参数是更新的内容,第二个参数是一个回调函数,当更新完成后会执行这个回调函数

setState() 会对一个组件的 state 对象安排一次更新。当 state 改变了,该组件就会重新渲染。

setState实际上就是将新的state与旧的进行替换,并且自动帮助我们拷贝没有改变的值(会自动拷贝第一层)

this.state={ //旧sate
	n:1,
	m:2
}
setState({n:2})//新state

当我使用setState({n:2})的时候,这里的旧state实际上已经被替换成了最新的state,而不是只旧对象的修改。而且react帮助我们将m:2给合并新对象里面了。

不过只能帮我们合并第一层的属性,假设现在我把state里面再加一层复杂类型的值

this.state={
	user:{
		n:1,
        m:2
	}
}
//这时候修改n的值
setState({user:{n:this.state.n+1}})
//你会发现m不见了

原因就是react会帮助我们自动复制第一层属性,例如this.state.user,但是不会帮助我们复制this.state.user.m

解决方法:我们使用...运算符自己手工拷贝一份

setState({user:{...this.state.user,n:this.state.n+1}})

这种写法就是先把user内的所有属性都拷贝进来,再把里面的n单独修改覆盖。

调用 setState 其实是异步的,不要指望在调用 setState 之后,this.state 会立即映射为新的值

当调用setState时,如果括号内是对象,假设我多次修改state

add() {
  // 注意:这样 *不会* 像预期的那样工作。
  this.setState({count: this.state.count + 1});
}

handle() {
  // 假设 `this.state.count` 从 0 开始。
  this.add();
  this.add();
  this.add();
  // 当 React 重新渲染该组件时,`this.state.count` 会变为 1,而不是你期望的 3。

  // 这是因为上面的 `add()` 函数是从 `this.state.count` 中读取数据的,
  // 但是 React 不会更新 `this.state.count`,直到该组件被重新渲染。
  // 所以最终 `add()` 每次读取 `this.state.count` 的值都是 0,并将它设为 1。

解决方法是在括号内使用函数(state)=>{count:state.count+1}

使用函数每次都会传参,传入最新的state,这样就避免掉始终到同一个this.state里面取一样的值的错误。

给 setState 传递一个对象与传递一个函数的区别是什么?

传递一个函数可以让你在函数内访问到当前的 state 的值。因为 setState 的调用是分批的,所以你可以链式地进行更新,并确保它们是一个建立在另一个之上的,这样才不会发生冲突

shouldComponentUpdate钩子

这个钩子在组件更新前执行,它是用来优化组件渲染的性能的。

如果返回true,就让浏览器更新UI

如果返回false,就让浏览器不更新UI

作用

它主要是用来手动设置是否要进行组件更新,我们可以根据应用场景灵活地设置是否让UI更新,以达到性能的优化

用法示例

class App2 extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 1 };
  }

  //   shouldComponentUpdate(nextProps, nextState) {
  //     if (this.state.count !== nextState.count) {
  //       return true;
  //     } else {
  //       return false;
  //     }
  //   }
  add = () => {
    this.setState((state, props) => {
      return { count: state.count + 0 }; //每次点击后加0,所以值是相同的
    });
  };

  render() {
    return (
      <div>
        {console.log("render了一次")}//这里表示render了
        <button onClick={this.add}>Count: {this.state.count}</button>
      </div>
    );
  }
}

如果不加shouldComponentUpdate函数的代码,那么上面的代码始终会触发render,因为每次state的地址切换掉了,react认为它已经不再是原来的state了,即使值是一样的。

如果我们加上shouldComponentUpdate函数的代码,那么就不会触发render函数,因为这相当于告诉react,

我这里的值是一样的,你不用再帮我执行render了

注:react会在render后将新的虚拟dom跟旧的相比对,如果发现是一样的,就不会更新UI

React.PureComponent

当然,如果觉得上面的代码很长,可以把React.Component改成React.PureComponent,效果是一样的

PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。 如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。

生命周期之render

用途:展示视图,创建虚拟dom对象

注意要点

只能有一个根元素。如果实在不行多一个根元素,可以使用<></>或者<React.Fragment></React.Fragment>

      <>
        <div></div>
        <button></button>
      </>

使用if-else

render() {
    let message;
    if (this.state.count === 1) {
      message = <div>是1</div>;
    } else {
      message = <span>是2</span>;
    }
    return (
      <>
        {message}
        <button></button>
      </>
    );
  }
}

上面的代码太长了,用三元表达式吧

render() {
    let message;
    return (
      <>
        {(message = this.state === 1 ? <div>是1</div> : <span>不是1</span>)}
        <button>按钮</button>
      </>
    );
  }

使用for

 render() {
    let arr = [];
    for (let i = 0; i < this.state.arr.length; i++) {
    //注意要写key
      arr.push(<div key={i}>{this.state.arr[i]}</div>);
    }
    return arr;
  }
}

使用for时,需要跟vue一样,添加上key以避免不必要的麻烦

使用map

render() {
    return this.state.arr.map((value, index) => {
      return <div key={index}>{value}</div>;
    });
  }

使用map跟上面的for循环返回一个数组效果是一样的

componentDidMount

这个生命周期是用于组件已经挂载到页面上去而执行的特殊函数,官方推荐在这里面执行ajax请求,第一次渲染会执行这个钩子

componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。

componentDidUpdate

第一次渲染不会执行这个钩子,当页面更新后会执行这个钩子,这里也可以用于ajax请求,一般是用来更新数据

在这里面执行setState可能会触发无限循环,因为一直更新了数据,页面无限更新。

shouldComponentUpdate钩子返回false则不会执行此钩子,因为render函数不会被执行,UI也不会被更新。

componentWillUnmount

组件即将被移出页面然后在内存中被销毁时,会执行此生命周期钩子。

它和componentDidMount是有对应关系的

比如说在componentDidMount中设置了timer,那么就应该在componentWillUnmount钩子中清除它,以免造成内存浪费

同理,ajax跟一些监听的取消也在这里面写

分阶段看声明周期钩子

参考文档

zh-hans.reactjs.org/docs/react-…

zh-hans.reactjs.org/docs/state-…

官方生命周期图谱