问题集锦1

129 阅读12分钟

1、什么是虚拟DOM?

虚拟dom(VDOM)是真实DOM的内存表示。它是一种变成概念,一种模式。它会和真实DOM同步,比如通过ReactDOM这种库,这个同步的过程叫做调和。

2、React中类组件和函数组件的区别?

类组件

  • 无论是使用函数或是类来声明一个组件,都不能修改它自己的props。

    • 所有React组件都必须是纯函数,并禁止修改其自身的props
  • React是单项数据流,父组件改变了属性,那么子组件视图就会更新。

    • 属性props是外界传递过来的,状态state是组件本身的,状态可以在组件中任意修改。
    • 组件的属性和状态改变都会更新视图。
class Home extends React.Component {
  render() {
    return (
      <h1>Welcome { this.props.name }</h1>
    );
  }
}
ReactDOM.render(<Home name='react' />, document.getElementById('root'));

函数组件

函数组件接收一个单一的props对象并返回了一个React元素。

function Home (props) {
  return <h1>Welcome {props.name}</h1>
}
ReactDOM.render(<Home name='react' />, document.getElementById('root'));

区别

  • 语法:

    两者最明显的不同就是在语法上,函数组件是个纯函数,它接收一个props对象返回一个react元素。而类组件需要去继承React.Component并且创建render函数返回react元素,这将会要更多的代码。

  • 状态管理

    由于函数组件是一个存放室,所以不能在组件中使用setState(),因此将函数组件称为无状态组件。

  • 生命周期

    在函数组件中不能使用生命周期钩子函数,原因和不能使用state一样,所有的生命周期钩子都来自于继承的React.Component中。

注意:

在react16.8版本中添加了hooks,使得我们在函数组件中使用useState钩子去管理state,使用useEffect钩子去使用生命周期函数,因此,2、3两点就不是他们的区别。从这个改版中我们可以看出作者更加看重函数组件。

  • 调用方式

    如果SayHi是一个函数,React需要调用它:

    // 你的代码 
    function SayHi() { 
        return <p>Hello, React</p> 
    } 
    // React内部 
    const result = SayHi(props) // » <p>Hello, React</p>
    

    如果SayHi是一个类,React需要先用new操作符将其实例化,然后调用刚才生成实例的render方法:

    // 你的代码 
    class SayHi extends React.Component { 
        render() { 
            return <p>Hello, React</p> 
        } 
    } 
    // React内部 
    const instance = new SayHi(props) // » SayHi {} 
    const result = instance.render() // » <p>Hello, React</p>
    

    函数组件重新渲染将重新调用组件方法返回新的react元素,类组件重新渲染将new一个新的组件实例,然后调用reader类方法返回react元素,这也说明为什么类组件中this是可变的。

3、高阶组件?

高阶组件就是一个函数,且该函数接收一个组件作为参数,并返回一个新的组件。基本上,这是从React的组件幸子派生的一种模式,我们称它们为“纯”组件,因为它们可以接收任何动态提供的子组件,但它们不会修改或者复制其输入组件的任何行为。

const EnhancedComponent = higherOrderComponent(WrappedComponent);
  • 高阶组件是React中用于复用组件逻辑的一种高级技巧
  • 高阶组件的参数为一个组件返回的一个新的组件
  • 组件是将props转化为UI,而高阶组件是将组件转化为另一个组件。

4、constructor中super与props参数一起使用的目的是什么?

在调用方法之前,之类构造函数无法使用this引用super()。

在ES6中,在子类的constructor中必须先调用super才能引用this。

  • 使用props

    class MyComponent extends React.Component {
        constructor(props) {
            super(props);
            console.log(this.props);  // Prints { name: 'sudheer',age: 30 }
        }
    }
    
  • 不适用props

    class MyComponent extends React.Component {
        constructor(props) {
            super();
            console.log(this.props); // Prints undefined
            // But Props parameter is still available
            console.log(props); // Prints { name: 'sudheer',age: 30 }
        }
    ​
        render() {
            // No difference outside constructor
            console.log(this.props) // Prints { name: 'sudheer',age: 30 }
        }
    }
    

5、 什么是受控组件?

非受控组件

非受控组件,即组件的状态不受React控制的组件,例如:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
​
class Demo1 extends Component {
    render() {
        return (
            <input />
        )
    }
}
​
ReactDOM.render(<Demo1/>, document.getElementById('content'))

在这个input输入框组件里,我们并没有干涉input中的value展示,即用户输入的内容都会展示在上面。如果我们通过props给组件设置一个初始默认值,defaultValue属性是React内部实现的一个属性,目的类似于input的placeholder属性。(此处如果使用vvalue代替defaultValue,会发现输入框的值无法改变。)

受控组件

受控组件就是组件的状态受React控制。上面提到过,既然通过设置input的value属性,无法改变输入框的值,那么我们把它和state结合在一起,再绑定onChange事件,实时更新value值就行了。

class Demo1 extends Component {
    constructor(props) {
        super(props);
        this.state = {
            value: props.value
        }
    }
​
    handleChange(e) {
        this.setState({
            value: e.target.value
        })
    }
​
    render() {
        return (
            <input value={this.state.value} onChange={e => this.handleChange(e)}/>
        )
    }
}

6、简述下React的生命周期?每个生命周期都做了什么?

image.png

挂载

当组件实例被创建并插入DOM中时,其生命周期调用顺序如下:

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

更新

当组件的props或state发生变化时会出发更新。组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

卸载

渲染过程,生命周期,或子组件的构造函数中跑出错误时,会调用如下方法:

  • static getDerviedStateFromError()
  • componentDidCatch()

render()

render()方法是class组件中唯一必须实现的方法。

当render被调用时,它会检查this.props和this.state的变化并返回以下类型之一:

  • React元素。通常通过JSX创建。会被React渲染为DOM节点,会被React渲染为自定义组件。
  • 数组是的render方法可以和那会多个元素。

render()函数应该为纯函数,这意味着在不修改组件state的情况下,每次嗲用时都返回相同的结果,并且它不会直接与浏览器交互。

如需与浏览器进行交互,请在componentDidMount()或者其他生命周期方法中执行你的操作。保持render()为纯函数。

constructor

如果不初始化state或不进行方法绑定,则不需要为Render组件实现构造函数。

在React组件挂载之前,会调用它的构造函数。在为React.Component子类实现构造函数时,应在其他语句之前调用super(props)。否则,this.props在构造函数中可能会出现未定义的bug。

通常,在React中,构造函数仅用一下两种情况:通过给this.state赋值对象来初始化内部state。

  • 为事件处理函数绑定实例
  • 在constructor()函数中不要调用setState()方法。如果你的组件需要使用内部state,请直接炸构造函数中为this.state赋值初始state。

只能在构造函数中直接为this.state赋值。如果在其他方法中赋值,你应该使用this.steState()代替。

componentDidMount()

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

这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅

你可以在 componentDidMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。请谨慎使用该模式,因为它会导致性能问题。通常,你应该在 constructor() 中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,你可以使用此方式处理。

componentDidUpdate()

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

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

你也可以在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,正如上述的例子那样进行处理,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。不要将 props “镜像”给 state,请考虑直接使用 props。 欲了解更多有关内容,请参阅为什么 props 复制给 state 会产生 bug。

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

componentWillUnmount()

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

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

shouldComponentUpdate()

根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。

当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。

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

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

我们不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。

目前,如果 shouldComponentUpdate() 返回 false,则不会调用 UNSAFE_componentWillUpdate(),render() 和 componentDidUpdate()。后续版本,React 可能会将 shouldComponentUpdate 视为提示而不是严格的指令,并且,当返回 false 时,仍可能导致组件重新渲染。

static getDerivedStateFromProps()

getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现 组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。

派生状态会导致代码冗余,并使组件难以维护。 确保你已熟悉这些简单的替代方案:

  • 如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改,请改用 componentDidUpdate。
  • 如果只想在 prop 更改时重新计算某些数据,请使用 memoization helper 代替。
  • 如果你想在 prop 更改时“重置”某些 state,请考虑使组件完全受控或使用 key 使组件完全不受控代替。

此方法无权访问组件实例。如果你需要,可以通过提取组件 props 的纯函数及 class 之外的状态,在getDerivedStateFromProps()和其他 class 方法之间重用代码。

请注意,不管原因是什么,都会在每次渲染前触发此方法。这与 UNSAFE_componentWillReceiveProps 形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用 setState 时。

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。

此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。

应返回 snapshot 的值(或 null)。

Error boundaries

Error boundaries 是 React 组件,它会在其子组件树中的任何位置捕获 JavaScript 错误,并记录这些错误,展示降级 UI 而不是崩溃的组件树。Error boundaries 组件会捕获在渲染期间,在生命周期方法以及其整个树的构造函数中发生的错误。

如果 class 组件定义了生命周期方法 static getDerivedStateFromError() 或 componentDidCatch() 中的任何一个(或两者),它就成为了 Error boundaries。通过生命周期更新 state 可让组件捕获树中未处理的 JavaScript 错误并展示降级 UI。

仅使用 Error boundaries 组件来从意外异常中恢复的情况;不要将它们用于流程控制。

static getDerivedStateFromError()

此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。

componentDidCatch()

此生命周期在后代组件抛出错误后被调用。 它接收两个参数:

  • error —— 抛出的错误。
  • info —— 带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息。

componentDidCatch() 会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况。

React 的开发和生产构建版本在 componentDidCatch() 的方式上有轻微差别。

在开发模式下,错误会冒泡至 window,这意味着任何 window.onerror 或 window.addEventListener('error', callback) 会中断这些已经被 componentDidCatch() 捕获的错误。

相反,在生产模式下,错误不会冒泡,这意味着任何根错误处理器只会接受那些没有显式地被 componentDidCatch() 捕获的错误。