那些年,本身没回答上来的react面试题

一、React中元素与组件的区别

那个时候刚学react,不知道面试官说的元素是什么,如今知道了,就是虚拟dom嘛。。。html

React 元素(React element)react

它是 React 中最小基本单位,咱们可使用 JSX 语法轻松地建立一个 React 元素:面试

const element = <div className="element">I'm element</div> 复制代码

React 元素不是真实的 DOM 元素,它仅仅是 js 的普通对象(plain objects),因此也没办法直接调用 DOM 原生的 API。上面的 JSX 转译后的对象大概是这样的:算法

{
    _context: Object,
    _owner: null,
    key: null,
    props: {
    className: 'element',
    children: 'I'm element' }, ref: null, type: "div" } 复制代码

除了使用 JSX 语法,咱们还可使用 React.createElement() 和 React.cloneElement() 来构建 React 元素。设计模式

React 组件api

React 中有三种构建组件的方式。React.createClass()、ES6 class和无状态函数。数组

一、React.createClass()浏览器

var Greeting = React.createClass({
  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});
复制代码

二、ES6 classbash

class Greeting extends React.Component{
  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
};
复制代码

三、无状态函数数据结构

无状态函数是使用函数构建的无状态组件,无状态组件传入props和context两个参数,它没有state,除了render(),没有其它生命周期方法。

function Greeting (props) {
  return <h1>Hello, {props.name}</h1>;
}
复制代码

四、PureComponent

除了为你提供了一个具备浅比较的shouldComponentUpdate方法,PureComponent和Component基本上彻底相同。

元素与组件的区别

组件是由元素构成的。元素数据结构是普通对象,而组件数据结构是类或纯函数。

二、请说一说Forwarding Refs有什么用

是父组件用来获取子组件的dom元素的,为何有这个API,缘由以下

// 例若有一个子组件和父组件,代码以下
子组件为:
class Child extends React.Component{
    constructor(props){
      super(props);
    }
    render(){
      return <input />
    }
 }

// 父组件中,ref:
class Father extends React.Component{
  constructor(props){
    super(props);
    this.myRef=React.createRef();
  }
  componentDidMount(){
    console.log(this.myRef.current);
  }
  render(){
    return <Child ref={this.myRef}/>
  }
}
复制代码

此时父组件的this.myRef.current的值是Child组件,也就是一个对象,若是用了React.forwardRef,也就是以下

// 子组件
const Child = React.forwardRef((props, ref) => (
    <input ref={ref} />
));
// 父组件
class Father extends React.Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    }
    componentDidMount() {
        console.log(this.myRef.current);
    }
    render() {
        return <Child ref={this.myRef} />
    }
}
复制代码

此时父组件的this.myRef.current的值是input这个DOM元素

三、简述一下virtual DOM 如何工做?

  • 当数据发生变化,好比setState时,会引发组件从新渲染,整个UI都会以virtual dom的形式从新渲染
  • 而后收集差别也就是diff新的virtual dom和老的virtual dom的差别
  • 最后把差别队列里的差别,好比增长节点、删除节点、移动节点更新到真实的DOM上

四、你对Fiber架构了解吗

注:答案参考司徒正美大神的文章

4.一、Fiber架构相对于之前的递归更新组件有有什么优点

  • 缘由是递归更新组件会让JS调用栈占用很长时间。
  • 由于浏览器是单线程的,它将GUI渲染,事件处理,js执行等等放在了一块儿,只有将它作完才能作下一件事,若是有足够的时间,浏览器是会对咱们的代码进行编译优化(JIT)及进行热代码优化。
  • Fiber架构正是利用这个原理将组件渲染分段执行,提升这样浏览器就有时间优化JS代码与修正reflow!

4.一、既然你说Fiber是将组件分段渲染,那第一段渲染以后,怎么知道下一段从哪一个组件开始渲染呢?

  • Fiber节点拥有return, child, sibling三个属性,分别对应父节点, 第一个孩子, 它右边的兄弟, 有了它们就足够将一棵树变成一个链表, 实现深度优化遍历。

4.2 怎么决定每次更新的数量

  • React16则是须要将虚拟DOM转换为Fiber节点,首先它规定一个时间段内,而后在这个时间段能转换多少个FiberNode,就更新多少个。
  • 所以咱们须要将咱们的更新逻辑分红两个阶段,第一个阶段是将虚拟DOM转换成Fiber, Fiber转换成组件实例或真实DOM(不插入DOM树,插入DOM树会reflow)。Fiber转换成后二者明显会耗时,须要计算还剩下多少时间。
  • 好比,能够记录开始更新视图的时间var now = new Date - 0,假如咱们更新试图自定义须要100毫秒,那么定义结束时间是var deadline = new Date + 100 ,因此每次更新一部分视图,就去拿当前时间new Date<deadline作判断,若是没有超过deadline就更新视图,超过了,就进入下一个更新阶段

4.3 如何调度时间才能保证流畅

  • 使用浏览器自带的api - requestIdleCallback,
  • 它的第一个参数是一个回调,回调有一个参数对象,对象有一个timeRemaining方法,就至关于new Date - deadline,而且它是一个高精度数据, 比毫秒更准确
  • 这个由于浏览器兼容性问题,react团队本身实现了requestIdleCallback

4.4 fiber带来的新的生命周期是什么

建立时

  • constructor ->
  • getDerivedStateFromProps(参数nextProps, prevState,注意里面this不指向组件的实例)->
  • render ->
  • componentDidMount

更新时

  • getDerivedStateFromProps(这个时props更新才调用,setState时不调用这个生命周期, 参数nextProps, prevState) ->
  • shouldComponentUpdate(setState时调用参数nextProps, nextState)->
  • render->
  • getSnapsshotBeforeUpdate(替换 componentWillUpdate)
  • componentDidUpdate(参数prevProps, prevState, snapshot)

五、什么是受控组件和非受控组件

  • 受状态控制的组件,必需要有onChange方法,不然不能使用 受控组件能够赋予默认值(官方推荐使用 受控组件) 实现双向数据绑定
class Input extends Component{
    constructor(){
        super();
        this.state = {val:'100'}
    }
    handleChange = (e) =>{ //e是事件源
        let val = e.target.value;
        this.setState({val});
    };
    render(){
        return (<div>
            <input type="text" value={this.state.val} onChange={this.handleChange}/>
            {this.state.val}
        </div>)
    }
}
复制代码
  • 非受控也就意味着我能够不须要设置它的state属性,而经过ref来操做真实的DOM
class Sum extends Component{
    constructor(){
        super();
        this.state =  {result:''}
    }
    //经过ref设置的属性 能够经过this.refs获取到对应的dom元素
    handleChange = () =>{
        let result = this.refs.a.value + this.b.value;
        this.setState({result});
    };
    render(){
        return (
            <div onChange={this.handleChange}>
                <input type="number" ref="a"/>
                {/*x表明的真实的dom,把元素挂载在了当前实例上*/}
                <input type="number" ref={(x)=>{
                    this.b = x;
                }}/>
                {this.state.result}
            </div>
        )
    }
}
复制代码

六、什么是状态提高

使用 react 常常会遇到几个组件须要共用状态数据的状况。这种状况下,咱们最好将这部分共享的状态提高至他们最近的父组件当中进行管理。咱们来看一下具体如何操做吧。

import React from 'react'
class Child_1 extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        return (
            <div>
                <h1>{this.props.value+2}</h1>
            </div> 
        )
    }
}
class Child_2 extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        return (
            <div>
                <h1>{this.props.value+1}</h1>
            </div> 
        )
    }
}
class Three extends React.Component {
    constructor(props){
        super(props)
        this.state = {
            txt:"牛逼"
        }
        this.handleChange = this.handleChange.bind(this)
    }
    handleChange(e){
        this.setState({
            txt:e.target.value
        })
    }
    render(){
       return (
            <div>
                <input type="text" value={this.state.txt} onChange={this.handleChange}/>
                <p>{this.state.txt}</p>
                <Child_1 value={this.state.txt}/>
                <Child_2 value={this.state.txt}/>
            </div>
       )
    }
}
export default Three
复制代码

七、什么是高阶组件

高阶组件不是组件,是 加强函数,能够输入一个元组件,返回出一个新的加强组件

  • 属性代理 (Props Proxy) 在我看来属性代理就是提取公共的数据和方法到父组件,子组件只负责渲染数据,至关于设计模式里的模板模式,这样组件的重用性就更高了
function proxyHoc(WrappedComponent) {
	return class extends React.Component {
		render() {
			const newProps = {
				count: 1
			}
			return <WrappedComponent {...this.props} {...newProps} />
		}
	}
}
复制代码
  • 反向继承
const MyContainer = (WrappedComponent)=>{
    return class extends WrappedComponent {
        render(){
            return super.render();
        }
    }
}
复制代码

八、什么是上下文Context

Context 经过组件树提供了一个传递数据的方法,从而避免了在每个层级手动的传递 props 属性。

  • 用法:在父组件上定义getChildContext方法,返回一个对象,而后它的子组件就能够经过this.context属性来获取
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Header extends Component{
    render() {
        return (
            <div>
                <Title/>
            </div>
        )
    }
}
class Title extends Component{
    static contextTypes={
        color:PropTypes.string
    }
    render() {
        return (
            <div style={{color:this.context.color}}>
                Title
            </div>
        )
    }
}
class Main extends Component{
    render() {
        return (
            <div>
                <Content>
                </Content>
            </div>
        )
    }
}
class Content extends Component{
    static contextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    render() {
        return (
            <div style={{color:this.context.color}}>
                Content
                <button onClick={()=>this.context.changeColor('green')}>绿色</button>
                <button onClick={()=>this.context.changeColor('orange')}>橙色</button>
            </div>
        )
    }
}
class Page extends Component{
    constructor() {
        super();
        this.state={color:'red'};
    }
    static childContextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    getChildContext() {
        return {
            color: this.state.color,
            changeColor:(color)=>{
                this.setState({color})
            }
        }
    }
    render() {
        return (
            <div>
                <Header/>
                <Main/>
            </div>
        )
    }
}
ReactDOM.render(<Page/>,document.querySelector('#root'));
复制代码

九、react中的Portal是什么?

Portals 提供了一种很好的将子节点渲染到父组件之外的 DOM 节点的方式。

ReactDOM.createPortal(child, container)
复制代码

第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。第二个参数(container)则是一个 DOM 元素。

十、react16的错误边界(Error Boundaries)是什么

部分 UI 中的 JavaScript 错误不该该破坏整个应用程序。 为了解决 React 用户的这个问题,React 16引入了一个 “错误边界(Error Boundaries)” 的新概念。

import React from 'react';
import ReactDOM from 'react-dom';
class ErrorBoundary extends React.Component{
    constructor(props) {
        super(props);
        this.state={hasError:false};
    }
    componentDidCatch(err,info) {
        this.setState({hasError: true});
    }
    render() {
        if (this.state.hasError) {
            return <h1>Something Went Wrong</h1>
        }
        return this.props.children;
    }
}

class Page extends React.Component{
    render() {
        return (
            <ErrorBoundary>
                <Clock/>
            </ErrorBoundary>
        )
    }
}
class Clock extends React.Component{
    render() {
        return (
            <div>hello{null.toString()}</div>
        )
    }
}

ReactDOM.render(<Page/>,document.querySelector('#root'));
复制代码

十一、如何在React中使用innerHTML

增长dangerouslySetInnerHTML属性,而且传入对象的属性名叫_html

function Component(props){
    return <div dangerouslySetInnerHTML={{_html:'<span>你好</span>'}}>
            </div>
}
复制代码

十二、react16版本的reconciliation阶段和commit阶段是什么

  • reconciliation阶段包含的主要工做是对current tree 和 new tree 作diff计算,找出变化部分。进行遍历、对比等是能够中断,歇一下子接着再来。
  • commit阶段是对上一阶段获取到的变化部分应用到真实的DOM树中,是一系列的DOM操做。不只要维护更复杂的DOM状态,并且中断后再继续,会对用户体验形成影响。在广泛的应用场景下,此阶段的耗时比diff计算等耗时相对短。

1三、请简单谈一下react的事件机制

  • 当用户在为onClick添加函数时,React并无将Click时间绑定在DOM上面。
  • 而是在document处监听全部支持的事件,当事件发生并冒泡至document处时,React将事件内容封装交给中间层SyntheticEvent(负责全部事件合成)
  • 因此当事件触发的时候,对使用统一的分发函数dispatchEvent将指定函数执行。

1四、为何列表循环渲染的key最好不要用index

举例说明

变化前数组的值是[1,2,3,4],key就是对应的下标:0,1,2,3
变化后数组的值是[4,3,2,1],key对应的下标也是:0,1,2,3
复制代码
  • 那么diff算法在变化前的数组找到key =0的值是1,在变化后数组里找到的key=0的值是4
  • 由于子元素不同就从新删除并更新
  • 可是若是加了惟一的key,以下
变化前数组的值是[1,2,3,4],key就是对应的下标:id0,id1,id2,id3
变化后数组的值是[4,3,2,1],key对应的下标也是:id3,id2,id1,id0
复制代码
  • 那么diff算法在变化前的数组找到key =id0的值是1,在变化后数组里找到的key=id0的值也是1
  • 由于子元素相同,就不删除并更新,只作移动操做,这就提高了性能
相关文章
相关标签/搜索