React

未分类
11k 词

React学习总结

一、邂逅React开发

1. React开发的三个依赖包是什么?分别有什么作用?

  • react:包含react所有必须的核心代码
  • react-dom:react 渲染在不同平台所需要的核心代码
  • babel:将jsx转换为 React 代码的工具

2. React如何封装组件,组件里面包含哪些内容?

  • 类的方式封装组件

  • 定义一个类(类名大写,组件的名称是必须大写的,小写会被认为是HTML元素),继承自React.Component

  • .实现当前组件的render函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class App extends React.Component{
    render(){
    return <h2> Hello React<h2/>
    }
    }
    #渲染根组件
    const root =ReactDOM.createRoot(document.querySelector("#r oot"))
    #使用组件
    root.render(<App/>)

3. 在进行函数绑定时,如何进行this关键字的绑定?

方式一:传入函数使用显示绑定(bind、call、apply)

  • 在传入函数时直接绑定this

    1
    2
    3
    4
    5
    6
    7
    8
    render() {
    return (
    <div>
    <h2>{this.state.message}</h2>
    <button onClick={this.btnClick.bind(this)}>点击</button>
    </div>
    )
    }

方式二:

  • 在类中提前绑定需要使用的函数的this

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
       class App extends React.Component {
    // 组件数据
    constructor() {
    super()
    this.state = {
    message: "Hello World",

    }

    // 对需要绑定的方法, 提前绑定好this
    this.btnClick = this.btnClick.bind(this)
    }

    render() {
    return (
    <div>
    <h2>{this.state.message}</h2>
    <button onClick={this.btnClick}>点击</button>
    </div>
    )
    }
    }

4. React如何进行列表数据的展示?回顾数组的常见高阶函数。

  • 方式一:直接使用for循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    render() {
    // 1.对movies进行for循环
    const liEls = []
    for (let i = 0; i < this.state.movies.length; i++) {
    const movie = this.state.movies[i]
    const liEl = <li>{movie}</li>
    liEls.push(liEl)
    }
    return (
    <div>
    <h2>电影列表</h2>
    <ul>
    <li> {liEls}</li>
    </ul>
    </div>
    )
    }
  • 方式二:map 高阶函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    render() 
    #返回一个新的数组
    const liEls = this.state.movies.map(movie => <li>{movie}</li>)

    return (
    <div>
    <h2>电影列表</h2>
    <ul>
    <li> {liEls}</li>
    </ul>
    </div>
    )
    }
  • 方式三: map 高阶函数 表达式

    1
    2
    3
    <ul>
    {this.state.movies.map(movie => <li>{movie}</li>)}
    </ul>

5. JSX如何绑定数据?如何绑定内容、属性,有哪些规则?

  • 绑定数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Home extends React.Component {
    constructor() {
    super();
    this.state = {
    message: 'BNTang'
    }
    }

    render() {
    return (
    <div>
    <p id="box">{this.state.message}</p>
    <p title={this.state.message}>{this.state.message}</p>
    </div>
    )
    }
    }

  • 绑定class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    class Home extends React.Component {
    constructor() {
    super();
    this.state = {
    isActive: true,
    objStyle: {color: "red", fontSize: "30px"}
    }
    }

    render() {
    const { title, imgURL, href, isActive, objStyle } = this.state
    #1.class绑定的写法一: 字符串的拼接
    const className = `abc cba ${isActive ? 'active': ''}`

    # 2.class绑定的写法二: 将所有的class放到数组中
    const classList = ["abc", "cba"]
    if (isActive) classList.push("active")

    # 3.class绑定的写法三: 第三方库classnames -> npm install classnames

    return (
    <div>
    <h2 className={className}>哈哈哈哈</h2>
    <h2 className={classList.join(" ")}>哈哈哈哈</h2>
    </div>
    )
    }
    }


  • 绑定style

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Home extends React.Component {
    constructor() {
    super();
    this.state = {
    message: 'BNTang'
    }
    }

    render() {
    return (
    <div>
    <p style={{color: 'red', fontSize: '100px'}}>{this.state.message}</p>
    </div>
    )
    }
    }
  • 绑定属性

    1
    2
    3
    <p title="我是标题">我是段落</p>

    <p title={message}>我是段落</p>
  • 书写规范

    • 顶层只有一个根元素
    • jsx 的外层需要包裹一个 () 方便阅读,可以进行换行书写
    • jsx的标签可以是单标签,也可以是双标签
    • 单标签必须以/>结尾
    • jsx中的注释 { /* */}
    • JSX嵌入变量作为子元素
      • 情况一:当变量是Number、String、Array类型时,可以直接显示
      • 情况二:当变量是null、undefined、Boolean类型时,内容为空;
        如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
        转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
      • 情况三:Object对象类型不能作为子元素(not valid as a React child)

6. JSX如何绑定数据?如何绑定内容、属性,有哪些规则?

二、React的JSX语法解析

1. JSX绑定事件,this绑定有哪些规则?如何给函数传递参数?

  • this绑定规则

    • 普通绑定 - onClick={this.btnClick} - 在内部是独立函数调用,所以this为undefined
    • this绑定方式一: bind绑定 - onClick={this.btnClick.bind(this)}
    • this绑定方式二: ES6 class fields - onClick={this.btnClick} - btnClick = () => {}
    • this绑定方式三: 直接传入一个箭头函数 - onClick={() => this.btnClick()}
  • 给函数传递参

    • event参数的传递 - onClick={(event) => this.btn1Click(event)}
    • 额外参数的传递 - onClick={(event) => this.btn2Click(event, "http", 18)}
    1
    2
    3
    4
    5
    6
    btn1Click(event) {
    console.log(event);
    }
    btn2Click(event, name, age) {
    console.log(event, name, age);
    }

2. JSX的代码是如何被编译为React代码的?它的本质是进行什么操作?

  • jsx是通过babel工具转换编译成React代码的
  • 本质
    • jsx 仅仅只是 React.createElement(component, props, …children) 函数的语法糖
    • 所有的jsx最终都会被转换成React.createElement的函数调用

3. 什么是虚拟DOM?虚拟DOM在React中起到什么作用?

  • 什么是虚拟DOM?
    • Virtual DOM 是一种编程概念,UI以一种理想化或者说虚拟化的方式保存在内存中
    • Virtual DOM 本质上是 JavaScript 对象,是真实 DOM 的描述,⽤⼀个 JS 对象来描述⼀个 DOM 节点
    • 我们知道jsx转成React代码的本质是 - 转换成React.createElement的函数调用
    • 通过React.createElement的函数创建出来的ReactElement对象
    • React利用ReactElement对象组成了一个JavaScript的对象树 - JavaScript的对象树就是虚拟DOM
  • 虚拟DOM在React中的作用
    • 虚拟DOM 通过diff算法 - 以最⼩的代价更新变化的视图
    • 跨平台渲染
    • 声明式编程 - 虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
      • 你只需要告诉React希望让UI是什么状态
      • React来确保DOM和这些状态是匹配的
      • 不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来

4. 分析脚手架创建的React项目目录结构,并且删除文件后自己编写代码

  • public
    • favicon.ico – 应用程序顶部icon图标
    • index.html – 应用的index.html入口文件
    • logo192.png – 在manifest.json中被使用
    • logo512.png – 在manifest.json中被使用
    • manifest.json – 与web app配置相关
    • robots.text – 指定搜索引擎可以或者不可以爬取那些信息
  • src
    • App.css – App组件相关样式
    • App.js – App组件代码文件
    • App.test.js – App组件的测试代码文件
    • index.css – 全局样式文件
    • index.js – 整个应用程序的入口文件
    • logo.svg – 启动项目时,所看到的React图标
    • reportWebVitals.js – 默认帮我们写好的 注册pwa相关的代码
    • setupTests.js – 测试初始文件
  • pwa是什么? 全称Progressive Web App,即渐进式WEB应用
    • 一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用
    • 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能
    • 这种Web存在的形式,我们也称之为是 Web App
    • pwa可以将网页添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏
    • pwa实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能
    • pwa实现了消息推送

三、React组件化开发(一)

1. React组件可以如何进行划分?分别有哪些不同的概念?

  • 根据定义方式

    • 函数组件
    • 类组件
  • 根据组件内部有无状态需要维护

    • 无状态组件
    • 有状态组件
  • 根据组件的不同职责

    • 展示型组件
    • 容器型组件
  • 关注UI的展示

    • 函数组件,无状态组件,展示型组件
  • 关注数据逻辑的展示

    • 类组件,有状态组件,容器型组件

2. React重要的组件生命周期有哪些?分别列出他们的作用。

  • componentDidMount
    • 会在组件挂载后立即调用
    • 操作DOM
    • 发送网络请求
  • componentDidUpdate
    • 会在组件更新后立即调用
    • 首次渲染不会执行此方法
    • 可以在组件更新后操作DOM
  • conponentWillUnmount
    • 会在组件卸载及销毁前调用
    • 清除定时器
    • 清除事件监听

3. React中如何实现组件间的通信?父传子?子传父?

  • 父传子
    • 在子组件通过属性传递内容
    • 子组件通过props拿到内容
  • 子传父
    • 通过传递函数,调用回调函数
    • 通过函数的参数传递要传递的数据

4. React中有插槽的概念吗?如果没有,如何实现插槽的效果呢?

  • 没有插槽的概念
  • 实现插槽效果的方式
    • 直接在组件中插入children
    • 在目标组件拿到插入的内容进行展示
  • 通过props直接将要展示的内容传递给子组件
    • 子组件通过props直接拿到要展示的内容

5. 非父子组件的通信有哪些方式?分别是什么作用?

  • 事件总线
  • 使用context
    • 创建context
    • 在要使用的组件,一般是根组件导入context
    • 使用<context.Provider>包裹后代组件
    • 在要使用的后代组件引入context
      • xxxx.contextType = context
      • 在render方法中可以通过this.context拿到传递过来的值

6. 面试题:React的setState是同步的还是异步的?React18中是怎么样的?

四、React组件化开发(二)

1. 非父子组件的通信有哪些方式?分别是什么作用?

  • Context
    • 1.创建Context
    • 2.使用<context.Provider>包裹后代组件
    • 3.在要使用的后代xxxx组件引入context
      • xxxx.contextType = context
      • 在render方法中可以通过this.context拿到传递过来的值
  • 事件总线EventBus
  • Redux

2. 面试题:React的setState是同步的还是异步的?React18中是怎么样的?

  • 在 React 中,可变状态通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新

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

  • 在react18之前, 在setTimeout,Promise等中操作setState, 是同步操作

  • 在react18之后, 在setTimeout,Promise等中操作setState,是异步操作(批处理)

    • 如果需要同步的处理怎么办呢? 需要执行特殊的flushSync操作
  • 为什么要将setState设计成异步的

    • 首先,若是将setState设计成同步的,在componentDidMount中请求多个网络请求时,会堵塞后面的网络请求
    1
    2
    3
    4
    5
    6
    componentDidMount() {
    // 网络请求一 : this.setState
    // 网络请求二 : this.setState
    // 网络请求三 : this.setState
    // 如果this.setState设计成同步的,会堵塞后面的网络请求
    }
    • 一. setState设计为异步,可以显著的提升性能

      • 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的
      • 最好的办法应该是获取到多个更新,之后进行批量更新
      1
      2
      3
      4
      5
      6
      // 在一个函数中有多个setState时,
      this.setState({}) --> 先不会更新,而是会加入到队列(queue)中 (先进先出)
      this.setState({}) --> 也加入到队列中
      this.setState({}) --> 也加入到队列中
      // 这里的三个setState会被合并到队列中去
      // 在源码内部是通过do...while从队列中取出依次执行的
    • 二: 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步

3. 什么是SCU优化?类组件和函数组件分别如何进行SCU的优化?

  • shouldComponentUpdate – SCU – React提供给我们的声明周期方法

  • SCU优化就是 一种巧妙的技术,用来减少DOM操作次数,具体为当React元素没有更新时,不会去调用render()方法

  • 可以通过shouldComponentUpdate来判断this.state中的值是否改变

    1
    2
    3
    4
    5
    6
    7
    shouldComponentUpdate(nextProps, nextState) {
    const {message, counter} = this.state
    if(message !== nextState.message || counter !== nextState.counter) {
    return true
    }
    return false
    }
  • React已经帮我们提供好SCU优化的操作

    • 类组件: 将class继承自PureComponent
    • 函数组件: 使用一个高阶组件memo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import {mome} from 'react'

    const HomeFunc = mome(function(props) {
    console.log("函数式组件")
    return (
    <h4>函数式组件: {props.message}</h4>
    )
    })

    export default HomeFunc

5. React为什么要强调不可变的力量?如何实现不可变的力量?

  • 不可变的力量: 不要直接去修改this.state中的值(主要指对象),若是想修改的话,应该是将这整个值全部修改掉
    • 注意: 值类型,在修改的时候,本身就全部替换掉了,所以不需要其他操作,直接改就可以
  • 实现: 将对象浅拷贝赋值给一个新的变量,在将这个新的变量赋值给this.state中的值

6. React中获取DOM的方式有哪些?

  • ref获取DOM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    getDOM() {
    // 方式一: 在react元素上绑定ref字符串 - 这种方式react已经不推荐了
    // console.log(this.refs.http)

    // 方式二: 提前创建好ref对象, createRef(), 将创建出来的对象绑定到元素(推荐)
    // console.log(this.titleRef.current)

    // 方式三: 传入一个回调函数, 在对应的元素被渲染之后, 回调函数被执行, 并且将元素传入(16.3之前的版本)
    // console.log(this.titleEl)
    }
    <h3 ref="http">大大怪将军</h3>
    <h3 ref={this.titleRef}>小小怪下士</h3>
    <h3 ref={el => this.titleEl = el}>瑞克</h3>
    <button onClick={() => this.getDOM()}>获取DOM</button>
  • ref获取组件实例 – createRef

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import React, { PureComponent, createRef } from 'react'

    constructor() {
    super()
    this.state = {}
    this.HWRef = createRef()
    }

    getComponent() {
    console.log(this.HWRef.current)
    this.HWRef.current.test()
    }

    <HelloWorld ref={this.HWRef} />
    <button onClick={() => this.getComponent()}>获取组件实例</button>
  • ref获取函数组件 – 函数式组件是没有实例的,所以无法通过ref获取他们的实例 – React.forwardRef

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import React, { PureComponent, createRef, forwardRef } from 'react'

    const HelloWorld = forwardRef(function(props, ref) {
    return (
    <div>
    <h2 ref={ref}>函数组件</h2>
    <h4>大大怪将军</h4>
    </div>
    )
    })
    constructor() {
    super()
    this.state = {}
    this.HWRef = createRef()
    }

    getComponent() {
    console.log(this.HWRef.current)
    }

    render() {
    return (
    <div>
    <HelloWorld ref={this.HWRef} />
    <button onClick={() => this.getComponent()}>获取DOM</button>
    </div>
    )
    }

7. 性能优化:React的diff算法和key的作用

  • React的渲染流程

    • 在render函数中返回jsx, jsx会创建出ReactElement对象(通过React.createElement的函数创建出来的)
    • ReactElement最终会形成一颗树结构, 这颗树结构就是vDOM
    • React会根据这样的vDOM渲染出真实DOM
  • React更新流程

    • props/state发生改变
    • render函数重新执行
    • 产生新的DOM树结构
    • 新旧DOM树 进行diff算法
    • 计算出差异进行更新
    • 最后更新到真实DOM
    1
    2
    3
    什么是diff算法?
    diff算法并非React独家首创,但是React针对diff算法做了自己的优化,使得时间复杂度优化成了O(n)
    对比两颗树结构,然后帮助我们计算出vDOM中真正变化的部分,并只针对该部分进行实际的DOM操作,而非渲染整个页面,从而保证了每次操作后页面的高效渲染。
  • React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树

  • React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI

  • 如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的**复杂程度为 O(n³)**,其中 n 是树中元素的数量

  • 如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围

  • 这个开销太过昂贵了,于是,React对这个算法进行了优化,将其优化成了O(n)

    • 同层节点之间相互比较,不会垮节点比较(一旦某个节点不同,那么包括其后代节点都会被替换)

      Snipaste_2022-09-02_15-53-43

    • 不同类型的节点,产生不同的树结构(当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树)

    • 开发中,可以通过key属性标识哪些子元素在不同的渲染中可能是不变的

  • 在遍历列表时,总是会提示一个警告,让我们加入一个key属性,当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。

    • 在最后位置插入数据 – 这种情况,有无key意义并不大
    • 在前面插入数据 – 这种做法,在没有key的情况下,所有的li都需要进行修改
    • 在中间插入元素 – 新增2014, key为2016元素仅仅进行位移,不需要进行任何的修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <ul>
    <li key="2015">Duke</li>
    <li key="2016">Villanova</li>
    </ul>

    <ul>
    <li key="2015">Duke</li>
    <li key="2014">Connecticut</li>
    <li key="2016">Villanova</li>
    </ul>
留言