# react基础2

# PropTypes 与 DefaultProps

cnpm i prop-types --save
  • ToDisplay.propTypes注意使用的时候首字母小写
//给父组件传过来的内容增加校验
import PropTypes from 'prop-types'
import React, { Component} from 'react';
class ToDisplay extends Component{
	render(){
		return(....)
	}
}

//子组件的名字ToDisplay
ToDisplay.propTypes={
	deleteli:PropTypes.func,
	index:PropTypes.number.isRequired,
	//isRequired:必须
	content:PropTypes.string
}
//若父组件未传值则增加默认值防止报warning
ToDisplay.defaultProps={
	index:1000
}

如果可能是多个类型,可以使用一下的arrayOf

ToDisplay.propTypes={
	deleteli:PropTypes.func,
	index:PropTypes.oneOfType([PropTypes.number,PropTypes.string])
	
}
import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // 你可以将属性声明为 JS 原生类型,默认情况下
  // 这些属性都是可选的。
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括数字、字符串、元素或数组)
  // (或 Fragment) 也包含这些类型。
  optionalNode: PropTypes.node,

  // 一个 React 元素。
  optionalElement: PropTypes.element,

  // 一个 React 元素类型(即,MyComponent)。
  optionalElementType: PropTypes.elementType,

  // 你也可以声明 prop 为类的实例,这里使用
  // JS 的 instanceof 操作符。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你可以让你的 prop 只能是特定的值,指定它为
  // 枚举类型。
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 一个对象可以是几种类型中的任意一个类型
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 可以指定一个数组由某一类型的元素组成
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 可以指定一个对象由某一类型的值组成
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 可以指定一个对象由特定的类型值组成
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // An object with warnings on extra properties
  optionalObjectWithStrictShape: PropTypes.exact({
    name: PropTypes.string,
    quantity: PropTypes.number
  }),

  // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
  // 这个 prop 没有被提供时,会打印警告信息。
  requiredFunc: PropTypes.func.isRequired,

  // 任意类型的必需数据
  requiredAny: PropTypes.any.isRequired,

  // 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
  // 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
  // 它应该在验证失败时返回一个 Error 对象。
  // 验证器将验证数组或对象中的每个值。验证器的前两个参数
  // 第一个是数组或对象本身
  // 第二个是他们当前的键。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

# react函数式组件props类型约束

import PropTypes from 'prop-types'

function HelloWorldComponent({ name }) {
  return (
    <div>Hello, {name}</div>
  )
}
//方法1:解构写在参数上,推荐
// function HelloWorldComponent({ name ='zhangsan' }) {
//   return (
//     <div>Hello, {name}</div>
//   )
// }

//方法2:不推荐
HelloWorldComponent.propTypes = {
  name: PropTypes.string
}

export default HelloWorldComponent

当父组件中已经给子组件设置了默认值,子组件再去设置的 所有默认都无效

// lifefather.js
import Lifechildren from './lifechildren'
//方法1:不推荐
Lifechildren.defaultProps ={
	name:'jack',
	// title : "百度科技父亲定义的",
}

class List extends Component {
  //方法2:推荐
  static defaultProps = {
    pageSize: 10
  }
  render() {
    return (
      <div>
        此处展示props的默认值:{this.props.pageSize}
      </div>
    )
  }
}
# diff算法
  • 同级比较(速度非常快)
  • key值比对
  • 多次结果一次替换,提升性能(setState异步操作的原因)

# ref的使用 React之ref操作DOM

reference简写,引用的意思,是React提供的用来操纵React类组件实例或者直接操作DOM元素的接口,尽量不使用。

它通常用于以下场景:

  • 直接操作 DOM:在某些情况下,你可能需要直接访问和操作 DOM 元素,例如设置焦点、测量元素的大小或位置等。通过 ref,你可以获取到这些 DOM 元素的引用,然后调用它们的原生方法或访问它们的属性。

  • 访问类组件实例:在类组件中,ref 可以用来访问组件的实例,从而可以调用实例上的方法

在函数组件中,通常使用 useRef 来创建 ref;在类组件中,可以使用 createRef 或在构造函数中初始化 ref。

import React, { useRef, useEffect } from 'react';  
  
function MyComponent() {  
  // 创建一个 ref  
  const inputRef = useRef(null);  
  
  useEffect(() => {  
    // 在组件挂载后,我们可以通过 ref 访问 DOM 元素  
    if (inputRef.current) {  
      inputRef.current.focus(); // 设置输入框焦点  
    }  
  }, []); // 空数组表示这个 effect 只在组件挂载和卸载时运行  
  
  return (  
    <div>  
      <input ref={inputRef} type="text" placeholder="Focus me on mount" />  
    </div>  
  );  
}  
  
export default MyComponent;

React.createRef() 函数创建的对象、或者一个回调函数、或者一个字符串(遗留 API)。当 ref 属性是一个回调函数时,此函数会(根据元素的类型)接收底层 DOM 元素或 class 实例作为其参数。这能够直接访问 DOM 元素或组件实例。

class MyClassComponent extends React.Component {  
  constructor(props) {  
    super(props);  
    this.myRef = React.createRef();  
  }  
  
  componentDidMount() {  
    if (this.myRef.current) {  
      this.myRef.current.focus();  
    }  
  }  
  
  render() {  
    return <input ref={this.myRef} type="text" />;  
  }  
}
import React, { Component, createRef } from 'react';  
  
// 类组件  
class MyClassComponent extends Component {  
  myMethod() {  
    console.log('My method was called!');  
    // 这里可以执行其他逻辑  
  }  
  
  render() {  
    return <div>This is a class component</div>;  
  }  
}  
  
// 父组件  
class ParentComponent extends Component {  
  constructor(props) {  
    super(props);  
    // 创建一个 ref  
    this.myClassComponentRef = createRef();  
  }  
  
  componentDidMount() {  
    // 通过 ref 访问类组件实例,并调用其方法  
    this.myClassComponentRef.current.myMethod();  
  }  
  
  render() {  
    return (  
      <div>  
        <MyClassComponent ref={this.myClassComponentRef} />  
      </div>  
    );  
  }  
}  
  
export default ParentComponent;
  • 回调 Refs
  • React.createRef() 创建引用
  • 和vue2那样this.$refs.xx的使用方法已经被废弃
<input	ref='k'/>
<!-- 废弃 -->

# createRef

创建一个能够通过 ref 属性附加到 React 元素的 ref,需要通过点current访问dom节点或组件实例。

ref 的值根据节点的类型而有所不同:

  • 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性(例如下面this.inputRef.current为input的dom节点)。

  • 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。

  • 不能在函数组件上使用 ref 属性,因为他们没有实例。

  • 为 DOM 元素添加 ref:使用 ref 去存储 DOM 节点的引用

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建一个 ref 来存储 textInput 的 DOM 元素
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:我们通过 "current" 来访问 DOM 节点
    this.textInput.current.focus();
  }

  render() {
    // 告诉 React 我们想把 <input> ref 关联到
    // 构造器里创建的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

# callback refs

“回调 refs”。它能更精细地控制何时 refs 被设置和解除。

不同于传递 createRef() 创建的 ref 属性,会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。

import React, { PureComponent } from 'react';
class App extends PureComponent {
    constructor(props) {
        super(props)
        this.titleRef = null     // 定义一个初始值
    }
    render() {
        return (
            <div>
                <h1 ref={arg => this.titleRef = arg}>Hello,React</h1>
                <button onClick={e => this.changeText()}>函数改变文本</button>
            </div>
        );
    }
    changeText() {
        console.log(this.titleRef)
        this.titleRef.innerHTML = "Hello"
    }
}
export default App;
import React,{Component} from 'react'

export default class Cc extends Component{
	constructor(arg) {
		super()
		this.state={inputvalue:'333'}
	}
	inputclick(){
			console.log(this.doinput)//<input>这个dom元素
			const value=this.doinput.value;
			this.setState(()=>{
				return{
					inputvalue:value
				}
			})
	}
	render(){
		return (<input 
				id="doinput"
				className="input"
				value={this.state.inputvalue}
				onChange={this.inputclick.bind(this)}
				ref={(input)=>{this.doinput=input}}
				{/*把input这个元素传给this.doinput这个变量进行处理*/}
			/>)
	}
}
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
      // 使用原生 DOM API 使 text 输入框获得焦点
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // 组件挂载后,让文本框自动获得焦点
    this.focusTextInput();
  }

  render() {
    // 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
    // 实例上(比如 this.textInput)
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。

可以在组件间传递回调形式的 refs,就像可以传递通过 React.createRef() 创建的对象 refs 一样。

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

在上面的例子中,Parent 把它的 refs 回调函数当作 inputRef props 传递给了 CustomTextInput,而且 CustomTextInput 把相同的函数作为特殊的 ref 属性传递给了 <input>。结果是,在 Parent 中的 this.inputElement 会被设置为与 CustomTextInput 中的 input 元素相对应的 DOM 节点。

TIP

如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle 结合使用)。

# function useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue通常为null)。返回的 ref 对象在组件的整个生命周期内持续存在。

它指向一个 DOM 元素或 class 组件:

function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它
  const textInput = useRef(null);

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

# React.forwardRef

将子组件ref暴露出去,在父组件上绑定ref获取子组件内绑定的ref。

函数组件通过React.forwardRef创建后,才可以在该组件上绑定ref属性,并且ref属性绑定的值是该组件内的ref绑定的dom节点而非该组件的实例。在该组件内ref只是回调ref函数,并不能在组件内访问。

import React, { useEffect } from 'react';  
  
const MyForwardedComponent = React.forwardRef((props, ref) => {  
  // 使用 ref 属性将 ref 绑定到 div 元素上  
  return <div ref={ref} tabIndex={0}>Forwarded Component</div>
})
  
let MyComponent = () => {  
  const myRef = React.createRef();  
  
  useEffect(() => {  
    // 在组件挂载后,通过 ref 访问 DOM 元素  
    const divElement = myRef.current;  
  
    // 手动设置焦点  
    divElement.focus();  
  
    // 获取 DOM 元素的大小,并在控制台中打印  
    const rect = divElement.getBoundingClientRect();  
    console.log('Div size:', rect);  
  }, []); // 空数组表示这个 effect 只在组件挂载和卸载时运行  
  
  return <MyForwardedComponent ref={myRef} />;  
}
  
export default MyComponent
import React, { PureComponent, forwardRef } from "react";
const Test = forwardRef(function Profile(props, ref) {
  React.useEffect(() => {
    console.log(ref); //arg => this.titleRef = arg
  }, []);
  return (
    <>
      <h1>h1</h1>
      <h2 ref={ref}>h2</h2>
    </>
  );
});
class App extends PureComponent {
  constructor(props) {
    super(props);
    this.titleRef = null; // 定义一个初始值
  }
  render() {
    return (
      <div>
        <Test ref={(arg) => (this.titleRef = arg)}></Test>
        <button onClick={(e) => this.changeText()}>函数改变文本</button>
      </div>
    );
  }
  changeText() {
    console.log(this.titleRef);
    this.titleRef.innerHTML = "Hello wrld";
  }
}
export default App;

# React.useImperativeHandle

搭配forwardRef一起使用,这是暴露在父组件的ref是useImperativeHandle函数第二个参数返回的对象。

import React, { PureComponent, forwardRef } from "react";
function FancyInput(props, ref) {
  const inputRef = React.useRef();
  React.useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    a(){
      console.log(inputRef)
    }
  }));
  return <input ref={inputRef }/>;
}
FancyInput = forwardRef(FancyInput);
class App extends PureComponent {
  constructor(props) {
    super(props);
    this.fancyRef = null; // 定义一个初始值
  }
  render() {
    return (
      <>
        <FancyInput ref={(arg) => (this.fancyRef = arg)} />
        <div onClick={()=>{this.changeText()}}>click</div>
      </>
    );
  }
  changeText() {
    console.log(this.fancyRef); //{focus: ƒ, a: ƒ}
    this.fancyRef.focus() //input聚焦
  }
}
export default App;

# react生命周期函数

# constructor

在 React 中,构造函数仅用于以下两种情况:

  • 通过给 this.state 赋值对象来初始化内部 state
  • 为事件处理函数绑定实例
constructor(props) {
 super(props);
 // 以下写法将props中的值写入state毫无必要(可以直接用 this.props.color)
 //同时还产生bug(更新 prop 中的 color 时,并不会影响 state)。
 this.state = { color: props.color };
}

生命周期函数指的是在某一个时刻react组件会自动调用执行的函数

  • componentWillMount==>组件即将被挂载到页面的时刻自动执行[react17移除]
  • componentDidMount==>组件被挂载到页面的之后自动执行
  • shouldComponentUpdate ==>组件被更新之前,自动执行
    • 需要返回一个布尔值(可以做拦截器)
    • 这是个提升性能优化的非常好的钩子函数,可以人为告诉某些地方不需要重新无效渲染,避免无效diff比对
  • componentWillUpdate===>组件被更新之前且shouldComponentUpdate返回值为true才会执行[react17移除]
  • componentDidUpdate===>组件更新完成之后执行
  • componentWillReceiveProps[react17移除]
    • 当一个组件要从父组件接受参数
    • 只要父组件的render函数被重新执行,子组件的这个生命周期就会被执行
    • 如果这个组件第一次存在父组件之中,不会执行
    • 如果之前已经存在,才会执行
  • componentWillUnmount
  • render===》渲染
  • getSnapshotBeforeUpdate()=>在最近一次渲染输出(提交到 DOM 节点)之前调用。
  • getDefaultProps:只适用React.createClass创建的类,其他想要使用可以用静态属性定义defaultProps代替
var ShowTitle = React.createClass({
  getDefaultProps:function(){
    return{
      title : "百度科技"
    }
  },
  render : function(){
    return <h1>{this.props.title}</h1>
  }
});
//静态属性非生命周期
static defaultProps={
	 title : "百度科技"
}
render(){
	return(
	<Fragment>
		<div>{this.props.title}---</div>
	</Fragment>
	)
}
  • getInitialState(es6中不使用):初始化state,es6可以在constructor中实现

# react生命周期图[老版本]

# 生命周期使用场景

  • 子组件如果没有发生变化,但是父组件render改变,也会触发子组件render变化,可以使用shouldComponentUpdate进行拦截处理;

此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。应该考虑使用内置的 PureComponent 组件 。PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。

如果一定要手动编写此函数,可以将 this.props 与 nextProps 以及 this.state 与nextState 进行比较,并返回 false 以告知 React 可以跳过更新。请注意,返回 false 并不会阻止子组件在 state 更改时重新渲染。

//性能优化
shouldComponentUpdate(nextProps,nextState){
		if(nextProps.content!==this.props.content){
			return true
		}else{
			return false
		}
		
}

# shouldComponentUpdate getDerivedStateFromProps 案例

import React,{Component} from 'react'
import C1 from './C1.js'

export default class P1 extends Component{
    constructor(props){
        super(props)
        this.state= {k:1}
    }
    render(){
        return (<div>
            <div>{this.state.k}</div>
            <button onClick={this.add}>+++</button>  
            <C1 {...this.state}/>
        </div>)
    }
    add = () => { 
        this.setState({
            k:this.state.k+1
        })
    }
    shouldComponentUpdate(nextProps,nextState){
       console.log('shouldcomponentupdate')
       // 如果父组件返回false,子组件的props.k就不会变化,不会重新渲染
       return true
    }
    static getDerivedStateFromProps(nextProps, prevState){
        console.log('getDerivedStateFromProps')
        // 这里虽然返回null,但是add函数修改了state,子组件依旧能获取新的props
        return null
    }
}
import React,{Component} from 'react'

export default class C1 extends Component{
    constructor(props){
        super(props)
        // console.log(this.props)
        this.state={foo:0}
    }
    render(){
        return (<div>
            <div>{this.props.k}</div>
            <br/>
            <div>{this.state.foo}</div>
        </div>)
    }
    static getDerivedStateFromProps(nextProps, prevState){
       // 当nextprops大于20时,this.state.foo就不再继续更新
       // this.props.k会继续更新
       console.log('child getDerived')
       if(nextProps.k>20){
        return null
       }
        return {foo:nextProps.k}
    }
  
}

react和vue父子组件更新的比较

  • vue 父子组件如果没有数据依赖,都只更新自己的数据,也就是beforeUpdate/updated只会本组件的会使用,如果父组件传递给子组件props而子组件并没有使用,也不会触发!(找时间验证下这句话)
  • 父组件更改,子组件会重新render,可使用此函数避免无效渲染,同时子组件render,不会触发父组件的render重新渲染;
  • componentDidMount

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

这个方法是比较适合添加订阅的地方。如果添加了订阅,一定在 componentWillUnmount() 里取消订阅

可以在 componentDidMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。 如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。谨慎使用该模式,它会导致性能问题。 通常,应在 constructor()中初始化state。

  • componentDidUpdate

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

componentDidUpdate(prevProps, prevState, snapshot)

当组件更新后,可以在此处对 DOM 进行操作。如果对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。 (例如,当 props 未发生变化时,则不会执行网络请求)。

componentDidUpdate(prevProps) {
  // 典型用法(不要忘记比较 props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

也可以在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里, 正如上述的例子那样进行处理,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。

如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用), 则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。

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

import React,{Component} from 'react';
class App extends Component{
	constructor(props) {
	    super(props);
		this.state={
			name:"zhangsan",
			age:31
		}
	}
	render(){
		console.log(111)
		return(
			<div>{this.state.name}</div>
		)
	}
	componentDidMount(){
		console.log(222);
		let _this = this;
		let num=0;
		setInterval(()=>{
			this.setState(()=>{
				return this.state.age++
			},()=>{
				if(this.state.age%2){
					this.setState(()=>{
						return{
							name:`wangqi${this.state.age}`
						}
					})
				}
			})
				
		},1000)
	
	}
	componentDidUpdate(pstate,pprops,p3){
		console.log(pstate)
		console.log(pprops)
		console.log(p3)
		console.log(333)
	}
	getSnapshotBeforeUpdate(prevProps, prevState){
		console.log(444)
		return "1"
	}
	shouldComponentUpdate(nextProps, nextState){
		console.log(555)
		console.log(nextProps, nextState)
		console.log(nextState.age)
		if(nextState.age%5){
			return true
		}
		return false
	}
}

export default App;
+ 首次会执行render,然后执行componentDidMount(仅首次)
+ 当需要更新前,执行shouldComponentUpdate,返回false,不更新,返回true,则会继续执行render,
+ 然后可选getSnapshotBeforeUpdate,再到componentDidUpdate
  • componentWillUnmount

componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

警告

  1. React16新的生命周期弃用了 componentWillMount、componentWillReceivePorps,componentWillUpdate
  2. 新增了 getDerivedStateFromProps、getSnapshotBeforeUpdate 来代替弃用的三个钩子函数
  3. React16并没有删除这三个钩子函数,但是不能和新增的钩子函数,React17将会删除
  4. 新增了对错误的处理(componentDidCatch)

组件生命周期的三个阶段

  • Mounting(加载阶段)
  • Updating(更新阶段)
  • Unmounting(卸载阶段)

# 新的生命周期

  • Mounting(加载阶段:涉及4个钩子函数)

    • constructor() 加载的时候调用一次,可以初始化state
    • static getDerivedStateFromProps(props, state) 组件每次被rerender的时候,包括在组件构建之后(虚拟dom之后,实际dom挂载之前),每次获取新的props或state之后;每次接收 新的props之后都会返回一个对象作为新的state返回null则说明不需要更新state ;配合componentDidUpdate,可以覆盖componentWillReceiveProps的所有用法
    static getDerivedStateFromProps(nextProps, prevState){
    	//没有this对象
    	//nextprops:拿到最新的props,和老的satte
    	console.log(nextProps, prevState)
    	prevState ={k:1000}
    	//去改变最新的state值
    	return {x:aa}
    }
    
    • render() react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行
    • componentDidMount() 组件渲染之后调用,只调用一次
  • Updating(更新阶段:涉及5个钩子函数)

    • static getDerivedStateFromProps(props, state)
    • shouldComponentUpdate(nextProps, nextState) 组件接收到新的props或者state时调用,return true就会更新dom(使用diff算法更新),return false能阻止更新(不调用render)
    • render()
    • getSnapshotBeforeUpdate(prevProps, prevState) 触发时间: update发生的时候,在render之后,在组件dom渲染之前;返回一个值,作为componentDidUpdate的第三个参数;配合componentDidUpdate, 可以覆盖componentWillUpdate的所有用法
    • componentDidUpdate() 组件加载时不调用,组件更新完成后调用
  • Unmounting(卸载阶段:涉及1个钩子函数) 组件渲染之后调用,只调用一次

  • Error Handling(错误处理)

    • componentDidCatch(error,info) 任何一处的javascript报错会触发
    • static getDerivedStateFromError():会在后代组件抛出错误后被调用

getSnapshotBeforeUpdate应用场景:通常和componentDidUpdate搭配!

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

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

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

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

# react中的动画 react-transition-group

  • Transition
  • CSSTransition
  • SwitchTransition
  • TransitionGroup
+ 想要首次也有动画效果,需要利用xx-appear
+ classNames="kk",那么类名都为kk-enter这种类型
+ 除了使用类名,也可以利用react-transition-group中钩子函数来进行处理动画
+ unmountOnExit:隐藏移除,显示展现,添加在<CSSTransition>上方便隐藏移除dom元素
+ in:如果是改变多个组件动画,那么外层需要包裹TransitionGroup,同时CSSTransition的in不需要了
 npm i react-transition-group --save
 npm i react-bootstrap --save
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { Container, Button, Alert } from 'react-bootstrap';
import { CSSTransition } from 'react-transition-group';

import './styles.css';

function Example() {
  const [showButton, setShowButton] = useState(true);
  const [showMessage, setShowMessage] = useState(false);
  return (
    <Container style={{ paddingTop: '2rem' }}>
      {showButton && (
        <Button
          onClick={() => setShowMessage(true)}
          size="lg"
        >
          Show Message
        </Button>
      )}
      <CSSTransition
        in={showMessage}
        timeout={300}
        classNames="alert"
        unmountOnExit
        onEnter={() => setShowButton(false)}
        onExited={() => setShowButton(true)}
      >
	  {/*
		in:如果是改变多个则不需要,但是外层需要加TransitionGroup标签(showMessage:需要改变的值)
		classNames:记住是classNames有s
		unmountOnExit:隐藏移除,显示展现
		CSSTransition:CSS三个字母都要大写!!!
		onEnter:钩子函数
	  */}
        <Alert
          variant="primary"
          dismissible
          onClose={() => setShowMessage(false)}
        >
          <Alert.Heading>
            Animated alert message
          </Alert.Heading>
          <p>
            This alert message is being transitioned in and
            out of the DOM.
          </p>
          <Button onClick={() => setShowMessage(false)}>
            Close
          </Button>
        </Alert>
      </CSSTransition>
    </Container>
  );
}

ReactDOM.render(
  <Example />,
  document.getElementById('root')
);

.alert-enter {
  opacity: 0;
  transform: scale(0.9);
}
.alert-enter-active {
  opacity: 1;
  transform: translateX(0);
  transition: opacity 300ms, transform 300ms;
}
.alert-exit {
  opacity: 1;
}
.alert-exit-active {
  opacity: 0;
  transform: scale(0.9);
  transition: opacity 300ms, transform 300ms;
}

+ onEnter
+ onEntering
+ onEntered
+ onExit
+ onExiting
+ onExited

新老版本生命周期 (opens new window)

# SwitchTransition可以完成两个组件之间切换的炫酷动画

比如有一个按钮需要在on和off之间切换,我们希望看到on先从左侧退出,off再从右侧进入

SwitchTransition中主要有一个属性mode,对应两个值:

  • in-out:表示新组件先进入,旧组件再移除;
  • out-in:表示就组件先移除,新组建再进入
  • SwitchTransition组件里面要有CSSTransition,不能直接包裹你想要切换的组件
  • 里面的CSSTransition组件不再像以前那样接受in属性来判断元素是何种状态,取而代之的是key属性
import React from 'react'
import './test.css'

import { SwitchTransition, CSSTransition } from "react-transition-group";

export default class SwitchAnimation extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      isOn: true
    }
  }

  render() {
    const {isOn} = this.state;

    return (
      <SwitchTransition mode="in-out">
        <CSSTransition classNames="btn"
                       timeout={500}
                       key={isOn ? "on1" : "off1"}>
          {
          <button onClick={this.btnClick.bind(this)}>
            {isOn ? "on1": "off1"}
          </button>
        }
        </CSSTransition>
      </SwitchTransition>
    )
  }

  btnClick() {
    this.setState({isOn: !this.state.isOn})
  }
}
.btn-enter {
  transform: translate(100%, 0);
  opacity: 0;
}

.btn-enter-active {
  transform: translate(0, 0);
  opacity: 1;
  transition: all 500ms;
}

.btn-exit {
  transform: translate(0, 0);
  opacity: 1;
}

.btn-exit-active {
  transform: translate(-100%, 0);
  opacity: 0;
  transition: all 500ms;
}

# TransitionGroup

当有一组动画的时候,就可将这些CSSTransition放入到一个TransitionGroup中来完成动画

同样CSSTransition里面没有in属性,用到了key属性

TransitionGroup在感知children发生变化的时候,先保存移除的节点,当动画结束后才真正移除

其处理方式如下:

  • 插入的节点,先渲染dom,然后再做动画
  • 删除的节点,先做动画,然后再删除dom
import './test.css'

import React, { PureComponent } from 'react'
import { CSSTransition, TransitionGroup } from 'react-transition-group';

export default class GroupAnimation extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      friends: []
    }
  }

  render() {
    return (
      <div>
        <TransitionGroup>
          {
            this.state.friends.map((item, index) => {
              return (
                <CSSTransition classNames="friend" timeout={300} key={index}>
                  <div>{item}</div>
                </CSSTransition>
              )
            })
          }
        </TransitionGroup>
        <button onClick={e => this.addFriend()}>+friend</button>
      </div>
    )
  }

  addFriend() {
    this.setState({
      friends: [...this.state.friends, "coderwhy"]
    })
  }
}
.friend-enter {
    transform: translate(100%, 0);
    opacity: 0;
}

.friend-enter-active {
    transform: translate(0, 0);
    opacity: 1;
    transition: all 500ms;
}

.friend-exit {
    transform: translate(0, 0);
    opacity: 1;
}

.friend-exit-active {
    transform: translate(-100%, 0);
    opacity: 0;
    transition: all 500ms;
}

# react传送门createPortal

import React from 'react'
import { createPortal } from 'react-dom'
class Dialog extends React.Component {
  constructor() {
    super(...arguments)

    const doc = window.document
    this.node = doc.createElement('div')
    doc.body.appendChild(this.node)

    window.document.body.appendChild(
      window.document.createElement('div')
    )
  }

  
  componentWillUnmount() {
    window.document.body.removeChild(this.node)
  }

  render() {
    return createPortal(
      <div class='dialog'>
        {this.props.children}
      </div>,
      this.node
    )
  }
}
最后更新: 11/15/2024, 6:15:27 PM