【react】react高频面试题

说一下react的 生命周期?

在这里插入图片描述

Mounting(挂载):已插入真实 DOM

如图所示:当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor(): 在 React 组件挂载之前,会调用它的构造函数。初始化数据
getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
render(): render() 方法是 class 组件中唯一必须实现的方法。初次渲染
componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。渲染之后触发事件
render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

Updating(更新):正在被重新渲染

每当组件的 stateprops 发生变化时,组件就会更新。
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
shouldComponentUpdate():当 propsstate 发生变化时, 会在渲染执行之前被调用
render(): render() 方法是 class 组件中唯一必须实现的方法。重新渲染
getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。
componentDidUpdate(): 在更新后会被立即调用。渲染之后触发事件
render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

Unmounting(卸载):已移出真实 DOM

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

componentWillUnmount(): 在组件卸载及销毁之前直接调用。

你用到过生命周期里的哪些函数?

在哪个生命周期中你会发出Ajax请求?

在组件尚未挂载之前,Ajax请求将无法执行完毕,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的
componentDidMount方法中,执行Ajax即可保证组件已经挂载,并且能够正常更新组件

使用shouldComponentUpdate对组件性能进行优化

react中,我们通过this.setState()方法去改变自身组件的state,以及子组件的props,然后触发组件重新渲染
那么,当我们setState之后,新的state和旧的state值是一样,页面也会进行重新渲染,这是不必要的,也是损耗性能的。
此时钩子函数:shouldComponentUpdate非常关键。
这个函数是re-render是render()函数调用前被调用的,他的两个参数nextProps和nextState,分别表示下一个props和下一个state的值。我们重写这个钩子,当函数返回false时,阻止接下来的render()调用以及组件重新渲染,反之,返回true时,组件向下走render重新渲染

所以我们可以在父组件中,重写这个方法:让他在前后name值不相等的时候才继续重新渲染

shouldComponentUpdate(nextProps, nextState) {
    if (nextState.name !== this.state.name) {
      return true
    }
    return false
  }

子组件中,这样写:

 shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.son !== this.props.son) {
      return true
    }
    return false
  }

JSX

什么是JSX?

1.jsx是JavaScript的一种语法扩展,它跟模板语言很接近,但是它充分具备JavaScript的能力。
2、Facebook公司给JSX的定位是JavaScript的扩展(直接决定了浏览器并不会像天然JavaScript一样地支持JSX,需要通过babel转译)

3、JSX会被babel编译为:React.createElement(),React.createElement()将返回一个叫作“ReactElement”的JS对象。

为什么 React 选择使用 JSX ?

JSX语法糖允许前端开发者使用我们最熟悉的类HTML标签语法来创建虚拟DOM在降低学习成本的同时,也提升了研发效率与研发体验。

如何使用JSX?

1、在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。

例如在标签里显示这个变量title:

const title = 'World'; const element = <h1>Hello, {title}</h1>;

2、在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。

例如:

1)你可以通过使用引号,来将属性值指定为字符串字面量:

 const element = <div tabIndex = "0"></div>;  

2)也可以使用大括号,来在属性值中插入一个 JavaScript 表达式:

const element = <img src = {user.avatarUrl}></img>;

3、因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

例如:JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex。

4、JSX中的标签可以是单标签,也可以是双标签,但必须保证标签是闭合的。

例如:

单标签:

const element = <img src = {user.avatarUrl }/>;

双标签:

const element = (<div>  <h1> Hello World ! </h1> </div>);

React有哪几种传值方式?

父组件传给子组件

//父组件中
<Child data={[1,2,3]} />
//子组件中
console.log(this.props.data);

子组件传给父组件

//父组件
import Child from './Child.js'export default class Parent extend compenent{
  getData=(data)=>{
    console.log(data);
  }
  render(){
    return (
      <div>
        父组件
        <Child getData={this.getData}/>
      </div>
    )
  }
}


//子组件
export default class Child extend compenent{
  state={
    data:[1,2,3]
  }
  render(){
    const {data}=this.state;
    return (
      <div>
        子组件
        <button onClick={()=>{this.props.getData(data)}}><button>
      </div>
    )
  }
}

路由传值(不同页面之间的传值)

props.params(推荐)

可以传递一个或多个值,但是每个值的类型都是字符串,没法传递一个对象,如果传递的话可以将json对象转换为字符串,然后传递过去,传递过去之后再将json字符串转换为对象将数据取出来

//定义路由
<Route path='/user/:data' component={UserPage}></Route>
//设置参数
var data = {id:3,name:sam,age:36};
data = JSON.stringify(data);
var path = `/user/${data}`;
//传值
<Link to={path}>用户</Link>
//或
hashHistory.push(path);
//获取参数
var data = JSON.parse(this.props.params.data);
var {id,name,age} = data;


state(不推荐,刷新页面参数丢失)
//设置路由
<Route path='/user' component={UserPage}></Route>
//设置参数
var data = {id:3,name:sam,age:36};
var path = {
  pathname:'/user',
  state:data,
}
//传值
<Link to={path}>用户</Link>
//或
hashHistory.push(path);
//获取参数
var data = this.props.location.state;
var {id,name,age} = data;

redux

非父子组件且嵌套关系复杂的组件之间数据的传递,暂不研究。

state和props它们的区别是什么和作用是什么?

1.props 是外部传递给组件的,而 state 是在组件内部组件自己管理的,一般在 constructor初始化

2.props 在组件内部是不可修改的,但 state 在组件内部可以进行修改

key的作用?

虚拟DOM中key的作用:

在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素。
比较规则如下:
a、旧虚拟DOM中找到了与新虚拟DOM相同的key:

(1)、如果虚拟DOM的内容没变,那么直接使用之前的真实DOM

(2)、如果虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b、旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到页面中

用index作为key可能会引发的问题

①、如果对数据进行:逆序添加、逆序删除等破坏顺序操作:

会产生没有必要的真实DOM更新 ==》界面效果没问题,但是效率很低

②、如果结构中还包含了输入类的DOM:

会产生错误DOM更新 ==》 界面有问题

③、注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,

仅用于渲染列表用于展示,使用index作为key是没有问题的

refs的作用?

Refs 是 React 中引用的简写。它是一个有助于存储对特定的 React 元素或组件的引用的属性,它将由组件渲染配置函数返回。用于对 render() 返回的特定元素或组件的引用。当需要进行 DOM 测量或向组件添加方法时,它们会派上用场。可以操作实际的dom点。

何时使用 Refs

  1. 管理焦点,文本选择或媒体交换
  2. 继承第三方
  3. DOM 库 触发强制动画

用法

1.回调形式的ref

在ref中传入一个回调函数,回调函数的默认参数就是元素对象,给组件加一个属性用来存储元素对象。

<div id="app"></div>    

<script type="text/babel">


    class Welcome extends React.Component{
        get=()=>{
            console.log(this.c.innerHTML)
        }
        render(){
            return(
                <div>
                    <div ref={a=>this.c=a}>hello</div>
                    <button onClick={this.get}>获取DOM元素</button>
                </div>
            )   
        }
    }
    
    ReactDOM.render(
        <Welcome/>,
        document.getElementById("app")
    )

</script>

2.React.createRef
//导入createRef方法
import React, { PureComponent,createRef } from 'react'  

//在构造器中
this.info = createRef() 

//标签中设置ref,值为构造函数中定义的info方法
<input type="text" ref={this.info} /> 

//函数中通过ref获取DOM中的值
getInputValue(){
    cnosole.log(this.refs.info.value) //打印出input中输入的内容
}

虚拟dom和真实dom的区别

什么是DOM

DOM document Object Model 文本对象模型 , 浏览器解析html标签形成类似树的结构

什么是虚拟DOM

虚拟DOM就是一个JS对象,通过对象的方式来表示DOM结构,通过事务处理机制,将多次DOM修改的结果一次性更新到页面上,从而有效的减少页面渲染次数,减少修改DOM重绘重排的时间,提高渲染性能。

区别:

虚拟DOM本质是Object类型的对象(一般对象)。
虚拟DOM比较“轻”,真实DOM比较“重”。因为虚拟DOM是React内部在使用,无需真实DOM上那么多的属性

虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

diff算法

React在内存中维护一个跟真实DOM一样的虚拟DOM树,再改动完组件后,.真实DOM首先映射为虚拟DOM,React会将新的虚拟DOM原来的虚拟DOM进行对比,找出两个DOM树的不同的地方(diff),然后在真实DOM上更新diff,提高渲染速度
在这里插入图片描述

在这里插入图片描述

具体流程如下:

1.真实DOM首先映射为虚拟DOM
2.当虚拟DOM发生变化时,根据变化计算生成patch,这个patch就是一个结构化的数据,包含了增加、更新、删除等操作。
3.根据patch去操作真实DOM,反馈到用户页面上。

setState的参数

setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行;
格式如下:setState(partialState, callback)
this.setState的第一个参数可以是一个对象,也可以是一个函数返回一个对象,函数的参数是上一次的state

// 对象
this.setState({count: this.state.count + 1});
// 函数
this.setState((prevState) => ({ prevState.count + 1 }));

第二个参数是一个callback函数,用于setState设置state的属性值成功之后的回调,此时调用this.state.property可以取到刚刚设置的最新的值

this.setState({
count: this.state.count + 1,
}, () => console.log(this.state.count)) //结果是+1之后的count

setState合并

this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })

虽然调用了三次 setState ,但是 count 的值还是为 1。因为多次调用会合并为一次,只有当更新结束后 state 才会改变。
三次调用等同于如下代码,Object.assign的对象合并是指:将源对象里面的属性添加到目标对象中去,若两者的属性名有冲突,后面的将会覆盖前面的

Object.assign( 
{},
{ count: this.state.count + 1 },
{ count: this.state.count + 1 },
{ count: this.state.count + 1 },
)

如果想让最后的结果等于3请用上面介绍的this.setState()的参数为函数返回对象的形式。

this.setState( prevState => ({ prevState.count + 1 }));
this.setState( prevState => ({ prevState.count + 1 }));
this.setState( prevState => ({ prevState.count + 1 }));

这样写就是3。

setState原理

在这里插入图片描述
react中,setState通过一个队列机制实现state的更新。当执行setState时,会把需要更新的state合并后放入状态队列,而不会立刻更新this.state,当进入组件批量可更新状态时,这个队列机制就会高效的批量的更新state。

setState是同步还是异步?

https://blog.csdn.net/wuyxinu/article/details/113902057
setState 只在生命周期,合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
合成事件:就是react 在组件中的onClick等都是属于它自定义的合成事件。
原生事件:比如通过addeventListener添加的,dom中的原生事件。

setState的异步并不是说内部由异步代码实现的,其实本身执行的过程和代码都是同步的,只是合
成事件和钩子函数调用的顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的
值,形成了所谓的“异步”,当然也可以通过第二个参数setState(partialState,callback)中的callback
拿到更新后的结果。

// 合成事件和钩子函数内setState采用回调函数同步获取
this.setState({   
      count: this.state.count + 1
},()=>{
     console.log(this.state.count)}) 
     
// setTimeout内是同步的
setTimeout(()=>{
      this.setState({count:this.state.count})
      console.log(this.state.count)
    },0) 
    
// 在原生事件中也是同步的
state = {
    count:0
};
componentDidMount() {
    document.body.addEventListener('click', this.changeVal, false);
}
changeVal = () => {
    this.setState({
      number: 1
    })
    console.log(this.state.count)
}

宏任务的时候是同步的,比如说setTimeOut里是同步的,但是在整个生命周期里是异步的。

常见面试题

class Example extends React.Component{
    constructor(){
    super(...arguments)
        this.state = {
            count: 0
        };
    }
    componentDidMount(){
       // a
      this.setState({count: this.state.count + 1});
      console.log('1:' + this.state.count)
      // b
      this.setState({count: this.state.count + 1});
      console.log('2:' + this.state.count)
      setTimeout(() => {
        // c
        this.setState({count: this.state.count + 1});
        console.log('3:' + this.state.count)
      }, 0)
      // d
      this.setState(preState => ({ count: preState.count + 1 }), () => {
        console.log('4:' + this.state.count)
      })
      console.log('5:' + this.state.count)
      // e
      this.setState(preState => ({ count: preState.count + 1 }))
      console.log('6:' + this.state.count)
    }
}

思考一下,你的答案是什么???
在这里插入图片描述

你的答案是否正确?你又是否理解为什么会出现上面的答案?接下来我们就来仔细分析一下。

setState(updater, [callback])
setState 可以接受两个参数,第一个参数可以是一个对象或者是一个函数,都是用来更新 state。第二个参数是一个回调函数(相当于Vue中的$NextTick ),我们可以在这里拿到更新的 state。

在上面的代码中,【a,b,c】的 setState 的第一个参数都是一个对象,【e,f】的 setState 的第一个参数都是函数。

首先,我们先说说执行顺序的问题。

【1,2,5,6】最先打印,【4】在中间,最后打印【3】。因为【1,2,5,6】是同步任务,【4】是回调,相当于 NextTick 微任务,会在同步任务之后执行,最后的【3】是宏任务,最后执行。

接下来说说打印的值的问题。

在【1,2,5,6】下面打印的 state 都是0,说明这里是异步的,没有获取到即时更新的值;

在【4】里面为什么打印出3呢?

首先在【a,b】两次 setState 时,都是直接获取的 this.state.count 的值,我们要明白,这里的这个值有“异步”的性质(这里的“异步”我们后面还会讲到),异步就意味着这里不会拿到能即时更新的值,那每次 setState 时,拿到的 this.state.count 都是0。

在【d,e】两个 setState 时,它的参数是函数,这个函数接收的第一个参数 preState (旧的 state ),在这里是“同步”的,虽有能拿到即时更新的值,那么经过【a,b】两次 setState (这里类似于被合并),这里即时的 count 还是1。因为上面我们说过的执行顺序的关系,再经过【d,e】两次 setState ,所以 count 变成了3。

那么在【3】中打印出4又是为什么?你不是说了在 this.state.count 中拿到的值是“异步”的吗,不是应该拿到0吗,怎么会打印出4呢?
在 setTimeout 执行的时候 isBatchingUpdate 是 false ,没有命中 batchUpdate 机制,这里的 this.state.count 已经是 3 了,所有在【3】中打印的就是 4。

setState为什么要异步?

可以显著的提升性能
如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;
最好的办法应该是获取到多个更新,之后进行批量更新

组件

什么是组件?

  • 组件就是页面上的一部分,可以是一个按钮、一张图片,可以是任意一个html元素。

react的纯组件,函数组件,类组件

纯组件:不受外界影响,自己内部更新。
函数组件:没有生命周期,不能根据这个进行传值更新,使用hook操作。
类组件:有生命周期,根据这个进行传值更新

纯组件

PureComponent 是 React 的内置组件,它会替代 shouldComponentUpdate 对 props 和 state 进行浅层比较。只要 state 和 props 一致,就不用再次 render,结果保持一致。

为什么要用纯组件?

纯组件内部自动实现了shouldComponentUpdate这个钩子函数,不需要手动比较。

浅层比较就是对引用地址的比较,主要分两类。

基本数据类型,就是单纯的值比较 1 == 1 true == true

引用类型,比如数组、对象就是地址的比较

class App extends React.PureComponent { 
 constructor(props){ 
    super(props) 
    this.state = { name:["gougou"] }    
    this.onclick = this.onclick.bind(this)  }  
    onclick(){ 
       this.state.name[0] = '勾勾';  
       this.setState({ name:this.state.name })  } 
       render(){ 
       return (  <div>  hello world 
       <p onClick = {this.onclick}>{this.state.name}</p>      
       </div>    
       )  
      }

​​上面这个案例的 name 是一个数组,再修改里面的第一个元素值为“勾勾”,但是这个引用地址还是之前的那个地址,所以当我们点击的时候,组件只是做了浅层对比,name 的引用地址是一样的。虽然内容不一样,但不会导致界面发生 render。

在这里插入图片描述

当我们了解了 Component 和 PureComponent 的基本情况后,就能尽量避免一些不必要的重复渲染。
React.PureComponent​​​与​​React.Component​​​很相似,两者的区别在于​​React.Component​​​并未实现​​shouldComponentUpdate()​​​,而​​React.PureComponent​​​中以浅层对比​​prop​​​和​​state​​​的方式来实现了该函数。如果赋予​​React​​​组件相同的​​props​​​和​​state​​​,​​render()​​​函数会渲染相同的内容,那么在某些情况下使用​​React.PureComponent​​可提高性能

函数组件(无状态组件)

什么是函数组件?

使用 JS 函数(普通,箭头)创建的组件

定义函数组件

创建函数组件,首字母大写,需要返回值,不渲染就返回 null 使用函数组件,组件名称当作标签使用即可

使用组件

函数的名称就是组件名称,使用组件就是把组件名称当标签使用即可。

import ReactDom from 'react-dom';
 
// 普通函数
function Header() {
  return <div>头部组件</div>;
}
 
// 箭头函数
const Footer = () => {
  return <div>底部组件</div>;
};
 
// 加载组件,不渲染内容
const Loading = () => {
  const loading = false;
  return loading ? <div>正在加载...</div> : null;
};
 
// 根组件
const App = () => {
  return (
    <>
      <Header />
      <Loading />
      <Footer />
    </>
  );
};
 
ReactDom.render(<App />, document.getElementById('root'));

类组件(有状态组件)

什么是类组件?

使用class语法创建的组件就是类组件

定义类组件

约定:类名首字母必需大写 约定:必须继承React.Component父类 约定:必需有render函数,返回 UI 结构,无渲染可返回null。

import { Component } from 'react';
 
class Header extends Component {
  render() {
    return <div>头部组件</div>;
  }
}
使用类组件

类名称就是组件名称,使用组件就是把组件名称当标签使用即可。

import { Component } from 'react';
import ReactDom from 'react-dom';
 
// 头部
class Header extends Component {
  render() {
    return <div>头部组件</div>;
  }
}
 
// 根组件
class App extends Component {
  render() {
    return (
      <>
        <Header />
      </>
    );
  }
}
 
ReactDom.render(<App />, document.getElementById('root'));

使用的时候把类名当作标签使用即可

区别

https://www.cnblogs.com/ximenchuifa/p/14913479.html?ivk_sa=1024320u
函数组件中的this是undefined,类组件中的this指向的是当前组件的实例对象。
类组件有生命周期,函数组件没有
类组件内部可以定义并维护 state, 函数组件为无状态组件(可以通过hooks实现)
类组件需要继承 Class,函数组件不需要

在react16.8版本中添加了hooks,使得我们可以在函数组件中使用useState钩子去管理state,使用useEffect钩子去使用生命周期函数。因此,第三点就不再是它们的区别点。

说明类组件函数组件
this有,事件处理函数需绑定
回调钩子生命周期useEffect / useLayoutEffect
state有,this.setState 更新无,useState / useReducer 引入
实例化
性能现代浏览器中,闭包和类的原始性能只有在极端场景才会有明显差别使用 Hooks 某些情况更加高效,避免了 class 需要的额外成本,如创建类实例和在构造函数绑定事件处理器的成本,符合语言习惯的代码不需要很深的组件库嵌套

高阶组件

高阶组件(HOC)是 React 用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数.其本身是纯函数,没有副作用。

组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。

function logProps(WrappedComponent) {
  return class extends React.Component {
    componentWillReceiveProps(nextProps) {
      console.log('Current props: ', this.props);
      console.log('Next props: ', nextProps);
    }
    render() {
      // 将 input 组件包装在容器中,而不对其进行修改。Good!
      return <WrappedComponent {...this.props} />;
    }
  }
}

React17 中对高阶组件的作用正确的是?

选项
A. 高阶组件是参数为组件,返回值为新组件的函数。

B. HOC 定义方式为修改传入的组件,或者通过继承方式进行复制。

C. 可用于渲染劫持。

D. 在 render 方法中使用 HOC 不会有性能问题。

答案
A、C

纠错

B. HOC 通过将组件包装在容器组件中来组成新组件。

C. 在 render 方法中使用 HOC 会有性能问题。

react优点,和vue的区别

共同点

  1. 都使用了 Virtual DOM
  2. 都提供了响应式和组件化的视图组件
  3. 将注意力集中保持在核心库,其他功能如路由和全局状态管理交给相关的库(具体解释下,需要查资料)

不同

优化: React 应用中,某个组件的状态发生改变时,它会以组件为根,重新渲染整个组件子树。
如果要避免不必要的子组件的渲染,需要使用 PureComponent 或者 shouldComponentUpdate 方法进行优化
在 Vue 应用中,组件的依赖是在渲染过程中自动跟踪的,所以系统能精确知道哪个组件需要被重新渲染

Hook

为什么 React 要加入 Hooks 特性?

React Hooks 就是让你不必写class组件就可以用state和其他的React特性;

  1. 完善函数组件的能力(没有生命周期,state),函数组件更适合 react 组件 。
  2. 组件逻辑复用,hooks表现更好。
  3. class复杂组件变的难以理解,逻辑混乱,不易拆解和测试
    例如:同样的逻辑,散落在 DidMount 和 DidUpdate 中,DidMount 和 WillUnMount 中,使用 hooks,同样的逻辑可以分割到一个一个的 useEffect 中(???)

首先,默认的函数组件是没有 state 的,因为函数组件是一个纯函数,执行完即销毁,无法存储 state,所以需要 State Hook,即把 state 功能“钩”到纯函数中,这样一来不影响函数组件是纯函数,也可以使用state

内置api

useEffect

调用时机:执行 DOM 更新之后调用。
相当于 componentDidMount 和 componentDidUpdate
可以写多个 useEffect 函数

hook版

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // 相当于 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 使用浏览器的 API 更新页面标题
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

class版

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

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

如上,函数组件需要在不同的声明周期写多次,而hook只需要写一次。

useRef

用来获取dom节点

import React, { useRef, useEffect } from 'react'

function UseRef() {
    const btnRef = useRef(null) // 初始值

    useEffect(() => {
        console.log(btnRef.current) // DOM 节点
    }, [])

    return <div>
        <button ref={btnRef}>click</button>
    </div>
}

export default UseRef


useContext

和 class 组件的 context 同样的效果,将状态传递给孙子组件

import React, { useContext } from 'react'

// 主题颜色
const themes = {
    light: {
        foreground: '#000',
        background: '#eee'
    },
    dark: {
        foreground: '#fff',
        background: '#222'
    }
}

// 创建 Context
const ThemeContext = React.createContext(themes.light) // 初始值

function ThemeButton() {
    const theme = useContext(ThemeContext)

    return <button style={{ background: theme.background, color: theme.foreground }}>
        hello world
    </button>
}

function Toolbar() {
    return <div>
        <ThemeButton></ThemeButton>
    </div>
}

function App() {
    return <ThemeContext.Provider value={themes.dark}>
        <Toolbar></Toolbar>
    </ThemeContext.Provider>
}

export default App


useReducer

import React, { useReducer } from 'react'

const initialState = { count: 0 }

const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 }
        case 'decrement':
            return { count: state.count - 1 }
        default:
            return state
    }
}

function App() {
    // 很像 const [count, setCount] = useState(0)
    const [state, dispatch] = useReducer(reducer, initialState)

    return <div>
        count: {state.count}
        <button onClick={() => dispatch({ type: 'increment' })}>increment</button>
        <button onClick={() => dispatch({ type: 'decrement' })}>decrement</button>
    </div>
}

export default App


https://blog.csdn.net/Jas3000/article/details/124168218
useReducer和redux的区别?

useReducer是useState的代替方案,用于 state 复杂变化 useReducer是单个组件状态管理,组件通讯还需要
props redux是全局的状态管理,多组件共享数据

useMemo

useMemo是针对一个函数,是否多次执行
useMemo主要用来解决使用React hooks产生的无用渲染的性能问题
在方法函数,由于不能使用shouldComponentUpdate处理性能问题,react hooks新增了useMemo
举例说明:

第一次进来时,控制台显示rich child,当无限点击按钮时,控制台不会打印rich child。但是当改变props.name为props.isChild时,每点击一次按钮,控制台就会打印一次rich child

export default () => {
	let [isChild, setChild] = useState(false);
 
	return (
		<div>
			<Child isChild={isChild} name="child" /> 
			<button onClick={() => setChild(!isChild)}>改变Child</button>
		</div>
	);
}
 
let Child = (props) => {
	let getRichChild = () => {
		console.log('rich child');
 
		return 'rich child';
	}
 
	let richChild = useMemo(() => {
		//执行相应的函数
		return getRichChild();
	}, [props.name]);  /*props.name是固定不变的"child",如果换成props.isChild  就大不同了*/
 
	return (
		<div>
			isChild: {props.isChild ? 'true' : 'false'}<br />
			{richChild}
		</div>
	);
}

useCallback

useCallback 和 useMemo 及区别

主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。

useCallback 使用场景:

有一个父组件,其中包含子组件,子组件接收一个函数作为 props ;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助 useCallback 来返回函数,然后把这个函数作为 props 传递给子组件;这样,子组件就能避免不必要的更新。

function Parent() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    const getNum = useCallback(() => {
        return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
    }, [count])
 
    return <div>
        <Child getNum={getNum} />
        <div>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}
 
const Child = React.memo(function ({ getNum }: any) {
    return <h4>总和:{getNum()}</h4>
})

注意事项

1.useState 初始化值,只有第一次有效
这个是子组件,初始化的时候值为userInfo.name,
当父组件传入的 userInfo 变化时,并不会修改 name 的值,也就是说 name 只能通过 setName 来修改。


function Child({ userInfo }) {
	const [name, setName] = useState(userInfo.name)
}

2.setState 是异步的

const [tabIndex, setTabIndex] = useState(0);

useEffect(() => {
  resetWhichContentList();
  getWhichContentList();
}, [tabIndex]);

const onChangeTab = async (data) => {
  setTabIndex(data.i);
  // 拿不到值
  console.log(tabIndex);
  // 本来在这里执行的逻辑放在 useEffect 中
};

3.useEffect 可能出现死循环

const [count, setCount] = useState(0);
const person = { name: "Rue", age: 17 }; //创建一个对象
useEffect(() => {
  // 每次增加count的值
  // person的值发生了变化
  setCount((count) => count + 1);
}, [person]); // 依赖项数组包含一个对象作为参数
return (
  <div className="App">
    <p> Value of {count} </p>
  </div>
);

是什么导致了这个问题?

和之前一样,React使用浅比较来检查person的参考值是否发生了变化
因为person对象的引用值在每次渲染时都会改变,所以React会重新运行useEffect
因此,在每个更新周期中调用setCount。这意味着我们现在有了一个无限循环

那么我们如何解决这个问题呢?

这就是usemmo的用武之地。当依赖关系发生变化时,这个钩子会计算一个记忆的值。除此之外,因为我们记住了一个变量,这确保了状态的引用值在每次渲染期间不会改变:

// 使用usemo创建一个对象
const person = useMemo(
  () => ({ name: "Rue", age: 17 }),
  [] //没有依赖关系,所以值不会改变
);
useEffect(() => {
  setCount((count) => count + 1);
}, [person]);

4.useEffect 内部不能修改 state

import React, { useState, useRef, useEffect } from 'react'

function UseEffectChangeState() {
    const [count, setCount] = useState(0)

    // 模拟 DidMount
    const countRef = useRef(0)
    useEffect(() => {
        console.log('useEffect...', count)

        // 定时任务
        const timer = setInterval(() => {
            console.log('setInterval...', countRef.current)
            // 这样改变 state 的值会有问题
            // setCount(count + 1)
            // 推荐用 useRef 来解决
            setCount(++countRef.current)
        }, 1000)

        // 清除定时任务
        return () => clearTimeout(timer)
    }, []) // 依赖为 []

    // 依赖为 [] 时: re-render 不会重新执行 effect 函数
    // 没有依赖:re-render 会重新执行 effect 函数

    return <div>count: {count}</div>
}

export default UseEffectChangeState


useEffect模拟组件生命周期

  1. 默认函数组件没有生命周期
  2. 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期
  3. 通过Effect hook把生命周期“钩”到纯函数中
    // 模拟 class 组件的 DidMount 和 DidUpdate
    useEffect(() => {
      console.log('在此发送一个 ajax 请求')
    })
 
    // // 模拟 class 组件的 DidMount
     useEffect(() => {
        console.log('加载完了')
     }, []) // 第二个参数是 [] (不依赖于任何 state)
 
     // 模拟 class 组件的 DidUpdate
     useEffect(() => {
         console.log('更新了')
     }, [count, name]) // 第二个参数就是依赖的 state
 
    // 模拟 class 组件的 DidMount
    useEffect(() => {
        let timerId = window.setInterval(() => {
            console.log(Date.now())
        }, 1000)
 
        // 返回一个函数
        // 模拟 WillUnMount 组件销毁的时候 停止计时器
        return () => {
            window.clearInterval(timerId)
        }
    }, [])

模拟componentDidMount - useEffect 依赖 [ ]
模拟compenentDidUpdate - useEffect 无依赖 ,或者 依赖 [a,b,c]
模拟componentWillUnMount - useEffect 中返回一个函数

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值