熟悉一下React的知识点(1)

118 阅读11分钟

React有哪些特性

  1. JSX 语法
  2. 单向数据绑定
  3. 虚拟 DOM (diff原理)
  4. 声明式编程
  5. Component

JSX语法

JSX = Javascript + XML

JSX 是一种 Javascript 的语法扩展,即在 Javascript 里面写 XML,JSX 实际是一种语法糖,在使用过程中会被 babel 进行编译转化成 JS 代码。

单向数据绑定(可以和vue做一下对比)

React是单向数据流,采用单向数据绑定,而 Vue 2.x 也是单向数据流,但同时支持单向数据绑定和双向数据绑定

所谓数据绑定,就是指View层和Model层之间的映射关系。

单向数据绑定: Model的更新会触发View的更新,而View的更新不会触发Model的更新,它们的作用是单向的。

双向数据绑定: Model的更新会触发View的更新,View的更新也会触发Model的更新,它们的作用是相互的。

React

在 React 中,View 层是不能直接修改 State,必须通过相应的 Actions 来进行操作,即通过调用 setState 方法对ViewState 进行更新,State更新后会触发View的重新渲染。

Vue

  • 单向数据绑定:使用v-bind属性绑定、v-on事件绑定或插值形式{{data}}
  • 双向数据绑定:使用v-model指令,用户对View的更改会直接同步到Model

 数据流

所谓数据流,是指组件之间的数据流动。

虽然 Vue有双向数据绑定,但 Vue父子组件之间数据传递,仍然遵循单向数据流,即父组件可以向子组件传递props,但是子组件不能修改父组件传递来的props,子组件只能通过事件通知父组件进行数据更改。

虚拟DOM

react和vue对虚拟DOM是一样的定义。

Virtual DOM是一棵以JavaScript对象作为基础的树,每一个节点可以将其称为VNode,用对象属性来描述节点,实际上它是一层对真实DOM的抽象,最终可以通过渲染操作使这棵树映射到真实环境上,简单来说Virtual DOM就是一个Js对象,是更加轻量级的对DOM的描述,用以表示整个文档。

虚拟dom优点:

1.减少操作真实dom。任何页面的变化,都只使用 VNode 进行操作对比,只需要在最后一步挂载更新dom,不需要频繁操作dom,从而提高页面性能。

2.跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染。

diff原理

VueReact都是通过引入Virtual DOM的概念,极大地避免无效的Dom操作,使页面的构建效率提到了极大的提升。

diff算法就是更高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处

不同之处

  • react 采用单指针从左向右进行遍历
  • vue采用双指针,从两头向中间进行遍历

相同之处

都只进行同级比较,忽略了跨级操作;常见的现象就是对数组或者对象中的深层元素进行修改时,视图层监测不到其变化,故不会对dom进行更新,需调用一些方法来告诉视图层需要更新dom。react中是setstate,vue中是vue.set。

声明式编程

声明式编程区别于命令式编程,它的特点就是我告诉计算机做什么,但是没有告诉你怎么做。

一般在声明式编程中,没有for或者if语句,而是会用map等函数,声明接收的结果,让计算机去执行一遍就可以了。

Component

React 的组件可以定义为 class 或函数的形式。就是一个UI的单元。目的是将要展示的内容,分成多个独立部分,每一个这样的部分,就是一个组件。class 组件目前提供了更多的功能。 关于生命周期,在下方介绍。

React生命周期

生命周期概念一般是存在与类组件当中,函数式组件需要借助hook(useEffect)来实现。

在类组件中,有新旧两版生命周期。

  • 旧版生命周期:react版本 < 16.0.0

  • 新版生命周期:react版本 >= 16.0.0

借助尚硅谷的图来理解。

旧版生命周期 react生命周期(旧).png

Mounting阶段

  1. constructor 同一个组件对象只会运行一次

不能在第一次挂载到页面之前,调用setState

  1. componentWillMount 正常情况下,和构造函数一样,它只会运行一次

可以使用setState,但是为了避免bug,不允许使用,因为在某些特殊情况下,该函数可能被调用多次 功能有点鸡肋,新版生命周期取消该钩子函数

  1. render react最重要的步骤,创建虚拟dom,进行diff算法,最终渲染到页面的真实DOM中。

render可能不止运行一次,只要需要重新渲染,就会重新运行,禁止在这里使用setState,可能导致无限递归。

  1. componentDidMount 可以使用setState,只会运行一次。

通常情况下,会将网络请求,启动计时器等一开始需要的操作,书写在该函数中。

Updating阶段

  1. componentWillReceiveProps(nextProps) 组件加载时不调用,组件接受新的props时调用(第一次不算),参数为新的属性对象。新版生命周期里官方取消了该钩子

  2. shouldComponentUpdate(nextProps,nextState) 是性能优化点,指示react是否要重新渲染该组件,通过返回true和false来指定,默认返回true

组件加载时不调用,只有组件将要更新时才会调用,值被覆盖原来的值也会调用。

  1. componentWillUpdate(nextProps,nextState) 组件更新时调用,可以setState。新版生命周期里官方取消了该钩子

  2. componentDidUpdate 组件更新完调用。 往往在该函数中使用dom操作,改变元素。

以下为Unmounting阶段:

  1. componentWillUnmount 组件即将卸载前调用,只调用一次。 通常在该函数中销毁一些组件依赖的资源,比如计时器

新版生命周期

react生命周期(新).png

从图中对比可以看出,React16弃用了三个生命周期钩子新增了两个生命周期钩子,弃用的三个生命周期钩子,在16之后使用要加上UNSAFE_前缀

弃用的三个生命周期:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

新增的两个生命周期:

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

static getDerivedStateFromProps(nextProps, prevState)

这个钩子函数,在挂载,更新时都会被调用。从props中获取state,这个生命周期的功能实际上就是将传入的props映射到state上面。 该函数是静态函数,所以取不到 this。如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾。

getSnapshotBeforeUpdate()

在最近的更改被提交到DOM元素前,使得组件可以在更改之前获得当前值,此生命周期返回的任意值都会传给componentDidUpdate()。在最新的渲染数据提交给DOM前会立即调用,它让你在组件的数据可能要改变之前获取他们。

state 和 props 区别

state

组件的显示可以由数据状态和外部参数所决定,而数据状态就是 state,一般在 constructor 中初始化。 需要修改里面的值的状态需要通过调用 setState 来改变,从而达到更新组件内部数据的作用,并且重新调用组件 render 方法。

props

React 的核心思想就是组件化思想,页面会被切分成一些独立的、可复用的组件。可以把 props 理解为从外部传入组件内部的数据。

react 具有单向数据流的特性,所以他的主要作用是从父组件向子组件中传递数据

props 除了可以传字符串,数字,还可以传递对象,数组甚至是回调函数

区别

  • props 是外部传递给组件的,而 state 是在组件内被组件自己管理的,一般在 constructor 中初始化
  • props 在组件内部是不可修改的(react是单向数据流),但 state 在组件内部可以进行修改

super() 和 super(props) 区别?

ES6中,extends实现类的继承,super 关键字实现调用父类,super 代替的是父类的构建函数,使用 super(name) 相当于调用 sup.prototype.constructor.call(this,name)

子类必须在constructor()中调用super() 方法否则新建实例 就会报错,报错的原因是 子类是没有自己的this对象的,它只能继承父类的this对象,然后对其进行加工,而super()就是将父类中的this对象继承给子类的,没有super() 子类就得不到this对象。

如果使用了constructor就必须写super(), 这个是用来初始化this的,可以绑定事件到this上。 如果要在constructor中使用this.props,就必须给super添加参数 super(props)

注意,无论有没有constructor,在render中的this.props都是可以使用的,这是react自动附带的 如果没有用到constructor 是可以不写的,react会默认添加一个空的constroctor.

setState

一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是state

当需要修改里面的值的状态需要通过调用setState来改变,从而达到更新组件内部数据的作用。感觉类似于Vue中的Vue.set()。

因为React并不像vue2中调用Object.defineProperty数据响应式或者Vue3调用Proxy监听数据的变化,必须通过setState方法来告知react组件state已经发生了改变。

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

在使用setState更新数据的时候,setState的更新类型分成:

  • 异步更新:在组件生命周期或React合成事件中
  • 同步更新:在setTimeout或者原生dom事件中(click)

设计异步更新原因是,如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;最好的办法应该是获取到多个更新,之后进行批量更新。

React事件绑定的方式

react应用中,事件名都是用小驼峰格式进行书写,事件绑定方式有四种

  • render方法中使用bind
  • render方法中使用箭头函数
  • constructor中bind
  • 定义阶段使用箭头函数绑定

1、render方法中使用bind(在组件每次render渲染的时候,都会重新进行bind的操作,影响性能)

    class App extends React.Component {
      handleClick() {
        console.log('this > ', this);
      }
      render() {
        return (
          <div onClick={this.handleClick.bind(this)}>test</div>
        )
      }
    }

2、render方法中使用箭头函数(通过ES6的上下文来将this的指向绑定给当前组件,同样再每一次render的时候都会生成新的方法,影响性能)

    class App extends React.Component {
      handleClick() {
        console.log('this > ', this);
      }
      render() {
        return (
          <div onClick={e => this.handleClick(e)}>test</div>
        )
      }
    }

3、constructor中bind(在constructor中预先bind当前组件,可以避免在render操作中重复绑定)

    class App extends React.Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
      }
      handleClick() {
        console.log('this > ', this);
      }
      render() {
        return (
          <div onClick={this.handleClick}>test</div>
        )
      }
    }

4、定义阶段使用箭头函数绑定

    class App extends React.Component {
      constructor(props) {
        super(props);
      }
      handleClick = () => {
        console.log('this > ', this);
      }
      render() {
        return (
          <div onClick={this.handleClick}>test</div>
        )
      }
    }

综合下来,第四种是最常用的,即写函数的时候使用箭头函数去操作。

react组件通信

类似于Vue,组件间通信的方式通常有以下几种:

  • 父子组件通信
  • 子父组件通信
  • 跨级组件通信
  • 非嵌套组件间通信
  • redux
  1. 父子组件通信(父组件通过向子组件传递 props,子组件得到 props 后进行相应的处理。)

        export default class App extends Component{              //父组件
            render(){
                return(
                    <div>
                        <Sub title = "你好" />
                    </div>
                )
            }
        }
    
    
        export default Sub = (props) => {                //子组件
            return(
                <h1>
                    { props.title }
                </h1>
            )
        }
    
    
  2. 子父组件通信(利用回调函数,可以实现子组件向父组件通信:父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信。)

        export default Sub = (props) => {                 //子组件
            const cb = (msg) => {                         //msg
                return () => {
                    props.callback(msg)
                }
            }
            return(
                <div>
                    <button onClick = { cb("你好") }>点击我</button>
                </div>
            )
        }
    
        export default class App extends Component{         //父组件
            callback=(msg)=>{
                console.log(msg);                           //msg
            }
            render(){
                return(
                    <div>
                        <Sub onClick = { this.callback} />
                    </div>
                )
            }
        }
    
  3. 跨级组件通信(使用 context 是一种可行的方式,context 相当于一个全局变量,是一个大容器,可以把要通信的内容放在这个容器中,不管嵌套有多深,都可以随意取用)

    使用 context需要满足两个条件:

  • 上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象
  • 子组件要声明自己需要使用 context
    export default class App extends Component{             
        static childContextTypes = {                 // 父组件声明自己支持 context
            color:PropTypes.string,
            callback:PropTypes.func,
        }
        
        getChildContext(){            // 父组件提供一个函数,用来返回相应的 context 对象
            return{
                color:"red",
                callback:this.callback.bind(this)
            }
        }
    
        callback(msg){
            console.log(msg)
        }
    
        render(){
            return(
                <div>
                    <Sub></Sub>
                </div>
            );
        }
    } 
    
    export default class SubSub extends Component{
        
        static contextTypes = {                  // 子组件声明自己需要使用 context
            color:PropTypes.string,
            callback:PropTypes.func,
        }
        render(){
            const style = { color:this.context.color }
            const cb = (msg) => {
                return () => {
                    this.context.callback(msg);
                }
            }
            return(
                <div style = { style }>
                    <button onClick = { cb("hello!") }>点击我</button>
                </div>
            );
        }
    }
    
    
  1. 非嵌套组件(需要借助一个帮手event)不展开叙述了
  2. redux(集中式管理)