React简单问题

255 阅读7分钟

为什么要引入React

在写React 的时候,我们可能会写类似这样的代码;

    import React from 'react';
    function App() {
        //...other code
        return <h1>前端唐卡</h1>
    }

既然上面的代码没有用到React,为啥还要引入React呢?

如果我们把import React from 'react'删掉,还会报React is not defined这样的错误;

那么我们究竟是哪里用到了这个React呢,导致我们引入React会报错呢,不懂这个问题,那就是jsx没有搞的明白。

你可以讲上面的代码(忽略导入语句)放到在线 babel 里进行转化一下,发现 babel 会把上面的代码转化成:

function A() {
  // ...other code
  return React.createElement("h1", null, "前端唐卡");
}

因为jsx的本质,只是为了React.createElement(component,props,...children)函数提供的语法糖。

为什么要用className而不是class

  1. React 一开始的理念是想与浏览器的DOM API保持一致而不是HTML,因为JSX是JS的扩展,而不是用来替代HTML的,这样会和元素的创建更为接近。在元素上设置class需要使用className:
    const element = document.createElement('div')
    element.className = 'hello'
  1. 浏览器问题,ES5前,在对象中不能使用保留字。以下代码在 IE8 中将会抛出错误:
    const element = {
        attributes: {
            class: 'hello'
        }
    }
  1. 解构问题,当你在解构属性的时候,class变量会报错:

为什么属性要用小驼峰

因为jsx语法更接近javascript 而不是html

为什么要在construction里调用super和传递props

我们要注意的是,类组件应始终使用props调用基础构造函数。

为什么要调用super

我们要在构造函数里面调用this,就必须提前调用super。

为什么要传递props

你可能以为必须给super传入props,否则React.Component就没法初始化this.props;

不过,如果你不小心漏传了props,直接调用了super(),你依然可以在后面的钩子函数中使用。

为啥这也行?因为React会在构造函数被调用后,会把props赋值给刚刚创建的实例对象:

const instance = new YourComponent(props);
instance.props = props;

为什么组件用大写开头

jsx是React.createElement(component,props,...children)提供的语法糖,component的类型是:string/ReactClass type,

  • string类型react会觉得他是一个原生的dom节点
  • ReactClass type 类型 自定义组件

例如(string): 在jsx中我们写一个

<div></div>

转换为js的时候就变成了

React.createElemnt('div',null)

例如(ReactClass Type):

function MyDiv() {
    return (<div></div>)
}
<MyDiv></MyDiv>

转换为js就变成了

function MyDiv() {
    return React.createElement('div',null);
}
React.createElement(MyDiv,null);

为什么调用方法要bind this

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。
这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this。

我们需要为事件处理函数绑定this就能解决,

class Foo extends React.Component {
  handleClick () {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
      <button onClick={this.handleClick.bind(this)}>
        Click me
      </button>
    )
  }
}

为什么要bind this呢?

React是如何处理事件的?

React的事件是合成事件,简单来说

 <button onClick={this.handleClick}>
     Click me
 </button>

在jsx React.createElement这个函数提供的语法糖。会被转化为

React.createElement("button", {
     onClick: this.handleClick
}, "Click me")

了解上面的,简单理解 react 如何处理事件的,React在组件加载(mount)和组件更新(update)时,将事件通过addEventListener统一注册到document上,然后会有一个事件池储存了所有的事件,当事件触发时,会通过dispatchEvent进行事件分发.

所以,你可以简单理解为,最终this.handleClick会作为一个回调函数调用。

this 简单回顾

class Foo {
  sayThis () {
    console.log(this); // 这里的 `this` 指向谁?
  }
  
  exec (cb) {
    cb();
  }
  
  render () {
    this.exec(this.sayThis);
  }
}

var foo = new Foo();
foo.render(); // 输出结果是什么?

你会发现最终结果输出的是 undefined,如果你不理解为什么输出的是 undefined,那么还是上面说的,需要去深刻的理解 this 的原理。如果你能理解输出的是 undefined,那么我觉得你就可以理解为什么需要 bind this 了。

那么你可能会问:**为什么React没有自动的把 bind 集成到 render 方法中呢?**在 exec 调用回调的时候绑定进去,

因为 render 多次调用每次都要 bind 会影响性能,所以官方建议你自己在 constructor 中手动 bind 达到性能优化。

四种事件处理对比

1. 直接 bind this 型

class Foo extends React.Component {
  handleClick () {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
      <button onClick={this.handleClick.bind(this)}>
        Click me
      </button>
    )
  }
}

缺点: 性能不太好,这种方式跟 react 内部帮你 bind 一样的,每次 render 都会进行 bind,而且如果有两个元素的事件处理函数式同一个,也还是要进行 bind,进行两次 bind,性能不是太好。

2. construction 手动 bind 型

class Foo extends React.Component {
  constuctor(props) {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick () {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    )
  }
}

缺点: 无

3. 箭头函数型

class Foo extends React.Component {
  handleClick () {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Click me
      </button>
    )
  }
}

缺点:每次render都会重复创建函数,性能会差一些。

4. pubilc class fields

class Foo extends React.Component {
  handleClick = () => {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    )
  }
}

缺点:需要多一个babel包来支持。正常都是这种写法

为什么要用setState

我们首先需要了解,setState不仅仅只是改变this.state的值。更为重要的是 它会触发React的更新机制,会进行diff,然后将patch部分更新到真实DOM里。

那为什么 Vue 直接修改 data 可以触发 UI 的更新呢?因为 Vue 在创建 UI 的时候会把这些 data 给收集起来,并且在这些 data 的访问器属性 setter 进行了重写,在这个重写的方法里会去触发 UI 的更新

setState同步、异步相关问题。

1. setState是同步还是异步?

执行过程代码是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法拿到更新后的值,形成了所谓的"异步",所以看起来,有时同步,有时异步。

2. 何时是同步,何时是异步呢?

只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout/setInterval等原生 API 中都是同步的。简单的可以理解为被 React 控制的函数里面就会表现出“异步”,反之表现为同步。

3. 为什么会出现异步的情况呢?

将 state 的更新延缓到最后批量合并再去渲染对于应用的性能优化是有极大好处的,如果每次的状态改变都去重新渲染真实 dom,那么它将带来巨大的性能消耗。

4. 如何在表现出异步的函数里可以准确拿到更新后的 state 呢?

通过第二个参数 setState(partialState, callback) 中的 callback 拿到更新后的结果。

5. 表现出异步的原理

在 React 的 setState 函数实现中,会根据 isBatchingUpdates(默认是 false) 变量判断是否直接更新 this.state 还是放到队列中稍后更新。然后有一个 batchedUpdate 函数,可以修改 isBatchingUpdates 为 true,当 React 调用事件处理函数之前,或者生命周期函数之前就会调用 batchedUpdate 函数,这样的话,setState 就不会同步更新 this.state,而是放到更新队列里面后续更新。

这样你就可以理解为什么原生事件和 setTimeout/setinterval 里面调用 this.state 会同步更新了吧,因为通过这些函数调用的 React 没办法去调用 batchedUpdate 函数将 isBatchingUpdates 设置为 true,那么这个时候 setState 的时候默认就是 false,那么就会同步更新。