关于react.js:前端工程师的20道react面试题自检

2次阅读

共计 11396 个字符,预计需要花费 29 分钟才能阅读完成。

什么是 React Fiber?

Fiber 是 React 16 中新的协调引擎或从新实现外围算法。它的次要指标是反对虚构 DOM 的增量渲染。React Fiber 的指标是进步其在动画、布局、手势、暂停、停止或重用等方面的适用性,并为不同类型的更新调配优先级,以及新的并发原语。
React Fiber 的指标是加强其在动画、布局和手势等畛域的适用性。它的次要个性是增量渲染: 可能将渲染工作宰割成块,并将其扩散到多个帧中。

Redux 原理及工作流程

(1)原理 Redux 源码次要分为以下几个模块文件

  • compose.js 提供从右到左进行函数式编程
  • createStore.js 提供作为生成惟一 store 的函数
  • combineReducers.js 提供合并多个 reducer 的函数,保障 store 的唯一性
  • bindActionCreators.js 能够让开发者在不间接接触 dispacth 的前提下进行更改 state 的操作
  • applyMiddleware.js 这个办法通过中间件来加强 dispatch 的性能
const actionTypes = {
    ADD: 'ADD',
    CHANGEINFO: 'CHANGEINFO',
}

const initState = {info: '初始化',}

export default function initReducer(state=initState, action) {switch(action.type) {
        case actionTypes.CHANGEINFO:
            return {
                ...state,
                info: action.preload.info || '',
            }
        default:
            return {...state};
    }
}

export default function createStore(reducer, initialState, middleFunc) {if (initialState && typeof initialState === 'function') {
        middleFunc = initialState;
        initialState = undefined;
    }

    let currentState = initialState;

    const listeners = [];

    if (middleFunc && typeof middleFunc === 'function') {
        // 封装 dispatch 
        return middleFunc(createStore)(reducer, initialState);
    }

    const getState = () => {return currentState;}

    const dispatch = (action) => {currentState = reducer(currentState, action);

        listeners.forEach(listener => {listener();
        })
    }

    const subscribe = (listener) => {listeners.push(listener);
    }

    return {
        getState,
        dispatch,
        subscribe
    }
}

(2)工作流程

  • const store= createStore(fn)生成数据;
  • action: {type: Symble(‘action01), payload:’payload’ }定义行为;
  • dispatch 发动 action:store.dispatch(doSomething(‘action001’));
  • reducer:解决 action,返回新的 state;

艰深点解释:

  • 首先,用户(通过 View)收回 Action,收回形式就用到了 dispatch 办法
  • 而后,Store 主动调用 Reducer,并且传入两个参数:以后 State 和收到的 Action,Reducer 会返回新的 State
  • State—旦有变动,Store 就会调用监听函数,来更新 View

以 store 为外围,能够把它看成数据存储核心,然而他要更改数据的时候不能间接批改,数据批改更新的角色由 Reducers 来负责,store 只做存储,中间人,当 Reducers 的更新实现当前会通过 store 的订阅来告诉 react component,组件把新的状态从新获取渲染,组件中也能被动发送 action,创立 action 后这个动作是不会执行的,所以要 dispatch 这个 action,让 store 通过 reducers 去做更新 React Component 就是 react 的每个组件。

高阶组件

高阶函数:如果一个函数 承受一个或多个函数作为参数或者返回一个函数 就可称之为 高阶函数

高阶组件:如果一个函数 承受一个或多个组件作为参数并且返回一个组件 就可称之为 高阶组件

react 中的高阶组件

React 中的高阶组件次要有两种模式:属性代理 反向继承

属性代理 Proxy

  • 操作 props
  • 抽离 state
  • 通过 ref 拜访到组件实例
  • 用其余元素包裹传入的组件 WrappedComponent

反向继承

会发现其属性代理和反向继承的实现有些相似的中央,都是返回一个继承了某个父类的子类,只不过属性代理中继承的是 React.Component,反向继承中继承的是传入的组件 WrappedComponent

反向继承能够用来做什么:

1. 操作 state

高阶组件中能够读取、编辑和删除 WrappedComponent 组件实例中的 state。甚至能够减少更多的state 项,然而 十分不倡议这么做 因为这可能会导致 state 难以保护及治理。

function withLogging(WrappedComponent) {    
    return class extends WrappedComponent {render() {    
            return (    
                <div>;    
                    <h2>;Debugger Component Logging...<h2>;    
                    <p>;state:<p>;    
                    <pre>;{JSON.stringify(this.state, null, 4)}<pre>;    
                    <p>props:<p>;    
                    <pre>{JSON.stringify(this.props, null, 4)}<pre>;    
                    {super.render()}    
                <div>;    
            );    
        }    
    };    
}

2. 渲染劫持(Render Highjacking)

条件渲染通过 props.isLoading 这个条件来判断渲染哪个组件。

批改由 render() 输入的 React 元素树

说说你用 react 有什么坑点?

1. JSX 做表达式判断时候,须要强转为 boolean 类型

如果不应用 !!b 进行强转数据类型,会在页面外面输入 0

render() {
  const b = 0;
  return <div>
    {!!b && <div> 这是一段文本 </div>}
  </div>
}

2. 尽量不要在 componentWillReviceProps 里应用 setState,如果肯定要应用,那么须要判断完结条件,不然会呈现有限重渲染,导致页面解体

3. 给组件增加 ref 时候,尽量不要应用匿名函数,因为当组件更新的时候,匿名函数会被当做新的 prop 解决,让 ref 属性承受到新函数的时候,react 外部会先清空 ref,也就是会以 null 为回调参数先执行一次 ref 这个 props,而后在以该组件的实例执行一次 ref,所以用匿名函数做 ref 的时候,有的时候去 ref 赋值后的属性会取到 null

4. 遍历子节点的时候,不要用 index 作为组件的 key 进行传入

参考:前端 react 面试题具体解答

React-Router 的实现原理是什么?

客户端路由实现的思维:

  • 基于 hash 的路由:通过监听 hashchange 事件,感知 hash 的变动

    • 扭转 hash 能够间接通过 location.hash=xxx
  • 基于 H5 history 路由:

    • 扭转 url 能够通过 history.pushState 和 resplaceState 等,会将 URL 压入堆栈,同时可能利用 history.go() 等 API
    • 监听 url 的变动能够通过自定义事件触发实现

react-router 实现的思维:

  • 基于 history 库来实现上述不同的客户端路由实现思维,并且可能保留历史记录等,磨平浏览器差别,下层无感知
  • 通过保护的列表,在每次 URL 发生变化的回收,通过配置的 路由门路,匹配到对应的 Component,并且 render

什么是状态晋升

应用 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

在 React 中组件的 this.state 和 setState 有什么区别?

this.state 通常是用来初始化 state 的,this.setState 是用来批改 state 值的。如果初始化了 state 之后再应用 this.state,之前的 state 会被笼罩掉,如果应用 this.setState,只会替换掉相应的 state 值。所以,如果想要批改 state 的值,就须要应用 setState,而不能间接批改 state,间接批改 state 之后页面是不会更新的。

React 和 vue.js 的相似性和差异性是什么?

相似性如下。
(1)都是用于创立 UI 的 JavaScript 库。
(2)都是疾速和轻量级的代码库(这里指 React 外围库)。
(3)都有基于组件的架构。
(4)都应用虚构 DOM。
(5)都能够放在独自的 HTML 文件中,或者放在 Webpack 设置的一个更简单的模块中。
(6)都有独立但罕用的路由器和状态治理库。
它们最大的区别在于 Vue. js 通常应用 HTML 模板文件,而 React 齐全应用 JavaScript 创立虚构 DOM。Vue. js 还具备对于“可变状态”的“reactivity”的从新渲染的自动化检测零碎。

React 中的 key 是什么?为什么它们很重要?

key 能够帮忙 React 跟踪循环创立列表中的虚构 DOM 元素,理解哪些元素已更改、增加或删除。
每个绑定 key 的虚构 DOM 元素,在兄弟元素之间都是举世无双的。在 React 的和解过程中,比拟新的虛拟 DOM 树与上一个虛拟 DOM 树之间的差别,并映射到页面中。key 使 React 解决列表中虛拟 DOM 时更加高效,因为 React 能够应用虛拟 DOM 上的 key 属性,疾速理解元素是新的、须要删除的,还是批改过的。如果没有 key,Rεat 就不晓得列表中虚构 DOM 元素与页面中的哪个元素绝对应。所以在创立列表的时候,不要疏忽 key。

React 16.X 中 props 扭转后在哪个生命周期中解决

在 getDerivedStateFromProps 中进行解决。

这个生命周期函数是为了代替 componentWillReceiveProps 存在的,所以在须要应用 componentWillReceiveProps 时,就能够思考应用 getDerivedStateFromProps 来进行代替。

两者的参数是不雷同的,而 getDerivedStateFromProps 是一个动态函数,也就是这个函数不能通过 this 拜访到 class 的属性,也并不举荐间接拜访属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,依据新传入的 props 来映射到 state。

须要留神的是,如果 props 传入的内容不须要影响到你的 state,那么就须要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的开端:

static getDerivedStateFromProps(nextProps, prevState) {const {type} = nextProps;
    // 当传入的 type 发生变化的时候,更新 state
    if (type !== prevState.type) {
        return {type,};
    }
    // 否则,对于 state 不进行任何操作
    return null;
}

react 性能优化是哪个周期函数

shouldComponentUpdate 这个办法用来判断是否须要调用 render 办法从新描述 dom。因为 dom 的描述十分耗费性能,如果咱们能在 shouldComponentUpdate 方 法中可能写出更优化的 dom diff 算法,能够极大的进步性能

跨级组件的通信形式?

父组件向子组件的子组件通信,向更深层子组件通信:

  • 应用 props,利用两头组件层层传递, 然而如果父组件构造较深,那么两头每一层组件都要去传递 props,减少了复杂度,并且这些 props 并不是两头组件本人须要的。
  • 应用 context,context 相当于一个大容器,能够把要通信的内容放在这个容器中,这样不论嵌套多深,都能够随便取用,对于逾越多层的全局数据能够应用 context 实现。
// context 形式实现跨级组件通信 
// Context 设计目标是为了共享那些对于一个组件树而言是“全局”的数据
const BatteryContext = createContext();
//  子组件的子组件 
class GrandChild extends Component {render(){
        return (
            <BatteryContext.Consumer>
                {color => <h1 style={{"color":color}}> 我是红色的:{color}</h1>
                }            </BatteryContext.Consumer>
        )
    }
}
//  子组件
const Child = () =>{
    return (<GrandChild/>)
}
// 父组件
class Parent extends Component {
      state = {color:"red"}
      render(){const {color} = this.state
          return (<BatteryContext.Provider value={color}>
              <Child></Child>
          </BatteryContext.Provider>
          )
      }
}

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

  • 受状态管制的组件,必须要有 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 中元素(element)和组件(component)有什么区别?

简略地说,在 React 中元素(虛拟 DOM)形容了你在屏幕上看到的 DOM 元素。
换个说法就是,在 React 中元素是页面中 DOM 元素的对象示意形式。在 React 中组件是一个函数或一个类,它能够承受输出并返回一个元素。
留神:工作中,为了进步开发效率,通常应用 JSX 语法示意 React 元素(虚构 DOM)。在编译的时候,把它转化成一个 React. createElement 调用办法。

介绍一下 react

  1. 以前咱们没有 jquery 的时候,咱们大略的流程是从后端通过 ajax 获取到数据而后应用 jquery 生成 dom 后果而后更新到页面当中,然而随着业务倒退,咱们的我的项目可能会越来越简单,咱们每次申请到数据,或则数据有更改的时候,咱们又须要从新组装一次 dom 构造,而后更新页面,这样咱们手动同步 dom 和数据的老本就越来越高,而且频繁的操作 dom,也使我咱们页面的性能缓缓的升高。
  2. 这个时候 mvvm 呈现了,mvvm 的双向数据绑定能够让咱们在数据批改的同时同步 dom 的更新,dom 的更新也能够间接同步咱们数据的更改,这个特定能够大大降低咱们手动去保护 dom 更新的老本,mvvm 为 react 的个性之一,尽管 react 属于单项数据流,须要咱们手动实现双向数据绑定。
  3. 有了 mvvm 还不够,因为如果每次有数据做了更改,而后咱们都全量更新 dom 构造的话,也没方法解决咱们频繁操作 dom 构造 (升高了页面性能) 的问题,为了解决这个问题,react 外部实现了一套虚构 dom 构造,也就是用 js 实现的一套 dom 构造,他的作用是讲实在 dom 在 js 中做一套缓存,每次有数据更改的时候,react 外部先应用算法,也就是鼎鼎有名的 diff 算法对 dom 构造进行比照,找到那些咱们须要新增、更新、删除的 dom 节点,而后一次性对实在 DOM 进行更新,这样就大大降低了操作 dom 的次数。那么 diff 算法是怎么运作的呢,首先,diff 针对类型不同的节点,会间接断定原来节点须要卸载并且用新的节点来装载卸载的节点的地位;针对于节点类型雷同的节点,会比照这个节点的所有属性,如果节点的所有属性雷同,那么断定这个节点不须要更新,如果节点属性不雷同,那么会断定这个节点须要更新,react 会更新并重渲染这个节点。
  4. react 设计之初是次要负责 UI 层的渲染,尽管每个组件有本人的 state,state 示意组件的状态,当状态须要变动的时候,须要应用 setState 更新咱们的组件,然而,咱们想通过一个组件重渲染它的兄弟组件,咱们就须要将组件的状态晋升到父组件当中,让父组件的状态来管制这两个组件的重渲染,当咱们组件的档次越来越深的时候,状态须要始终往下传,无疑加大了咱们代码的复杂度,咱们须要一个状态管理中心,来帮咱们治理咱们状态 state。
  5. 这个时候,redux 呈现了,咱们能够将所有的 state 交给 redux 去治理,当咱们的某一个 state 有变动的时候,依赖到这个 state 的组件就会进行一次重渲染,这样就解决了咱们的咱们须要始终把 state 往下传的问题。redux 有 action、reducer 的概念,action 为惟一批改 state 的起源,reducer 为惟一确定 state 如何变动的入口,这使得 redux 的数据流十分标准,同时也暴露出了 redux 代码的简单,原本那么简略的性能,却须要实现那么多的代码。
  6. 起初,社区就呈现了另外一套解决方案,也就是 mobx,它推崇代码简洁易懂,只须要定义一个可观测的对象,而后哪个组价应用到这个可观测的对象,并且这个对象的数据有更改,那么这个组件就会重渲染,而且 mobx 外部也做好了是否重渲染组件的生命周期 shouldUpdateComponent,不倡议开发者进行更改,这使得咱们应用 mobx 开发我的项目的时候能够简略疾速的实现很多性能,连 redux 的作者也举荐应用 mobx 进行我的项目开发。然而,随着我的项目的一直变大,mobx 也一直暴露出了它的毛病,就是数据流太随便,出了 bug 之后不好追溯数据的流向,这个毛病正好体现出了 redux 的长处所在,所以针对于小我的项目来说,社区举荐应用 mobx,对大我的项目举荐应用 redux

state 和 props 触发更新的生命周期别离有什么区别?

state 更新流程: 这个过程当中波及的函数:

  1. shouldComponentUpdate: 当组件的 state 或 props 产生扭转时,都会首先触发这个生命周期函数。它会接管两个参数:nextProps, nextState——它们别离代表传入的新 props 和新的 state 值。拿到这两个值之后,咱们就能够通过一些比照逻辑来决定是否有 re-render(重渲染)的必要了。如果该函数的返回值为 false,则生命周期终止,反之持续;

留神:此办法仅作为 性能优化的形式 而存在。不要希图依附此办法来“阻止”渲染,因为这可能会产生 bug。应该 思考应用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()

  1. componentWillUpdate:当组件的 state 或 props 产生扭转时,会在渲染之前调用 componentWillUpdate。componentWillUpdate 是 React16 废除的三个生命周期之一。过来,咱们可能心愿能在这个阶段去收集一些必要的信息(比方更新前的 DOM 信息等等),当初咱们齐全能够在 React16 的 getSnapshotBeforeUpdate 中去做这些事;
  2. componentDidUpdate:componentDidUpdate() 会在 UI 更新后会被立刻调用。它接管 prevProps(上一次的 props 值)作为入参,也就是说在此处咱们依然能够进行 props 值比照(再次阐明 componentWillUpdate 的确鸡肋哈)。

props 更新流程: 绝对于 state 更新,props 更新后惟一的区别是减少了对 componentWillReceiveProps 的调用。对于 componentWillReceiveProps,须要晓得这些事件:

  • componentWillReceiveProps:它在 Component 承受到新的 props 时被触发。componentWillReceiveProps 会接管一个名为 nextProps 的参数(对应新的 props 值)。该生命周期是 React16 废除掉的三个生命周期之一。在它被废除前,能够用它来比拟 this.props 和 nextProps 来从新 setState。在 React16 中,用一个相似的新生命周期 getDerivedStateFromProps 来代替它。

为什么要应用 React. Children. map(props. children,()=>)而不是 props. children. map (() => )?

因为不能保障 props. children 将是一个数组。
以上面的代码为例。

<Parent>
    <h1> 有课前端网 </h1>
</Parent>

在父组件外部,如果尝试应用 props.children. map 映射子对象,则会抛出谬误,因为 props. children 是一个对象,而不是一个数组。
如果有多个子元素,React 会使 props.children 成为一个数组,如下所示。

<Parent>
  <h1> 有课前端网 </h1>
  <h2> 前端技术学习平台 </h2>
</Parent>;
// 不倡议应用如下形式,在这个案例中会抛出谬误。class Parent extends Component {render() {return <div> {this.props.children.map((obj) => obj)}</div>;
  }
}

倡议应用如下形式,防止在上一个案例中抛出谬误。

class Parent extends Component {render() {return <div> {React.Children.map(this.props.children, (obj) => obj)}</div>;
  }
}

React-Router 4 的 Switch 有什么用?

Switch 通常被用来包裹 Route,用于渲染与门路匹配的第一个子 <Route><Redirect>,它外面不能放其余元素。

如果不加 <Switch>

import {Route} from 'react-router-dom'

<Route path="/" component={Home}></Route>
<Route path="/login" component={Login}></Route>

Route 组件的 path 属性用于匹配门路,因为须要匹配 /Home,匹配 /loginLogin,所以须要两个 Route,然而不能这么写。这样写的话,当 URL 的 path 为“/login”时,<Route path="/" /><Route path="/login" /> 都会被匹配,因而页面会展现 Home 和 Login 两个组件。这时就须要借助 <Switch> 来做到只显示一个匹配组件:

import {Switch, Route} from 'react-router-dom'

<Switch>
    <Route path="/" component={Home}></Route>
    <Route path="/login" component={Login}></Route>
</Switch>

此时,再拜访“/login”门路时,却只显示了 Home 组件。这是就用到了 exact 属性,它的作用就是准确匹配门路,常常与<Switch> 联结应用。只有当 URL 和该 <Route> 的 path 属性完全一致的状况下能力匹配上:

import {Switch, Route} from 'react-router-dom'

<Switch>
   <Route exact path="/" component={Home}></Route>
   <Route exact path="/login" component={Login}></Route>
</Switch>

diff 算法?

  • 把树形构造依照层级合成,只比拟同级元素。
  • 给列表构造的每个单元增加惟一的 key 属性,不便比拟。
  • React 只会匹配雷同 classcomponent(这外面的 class 指的是组件的名字)
  • 合并操作,调用 componentsetState 办法的时候, React 将其标记为 – dirty. 到每一个事件循环完结, React 查看所有标记 dirtycomponent从新绘制.
  • 抉择性子树渲染。开发人员能够重写 shouldComponentUpdate 进步 diff 的性能

React 性能优化

  • shouldCompoentUpdate
  • pureComponent 自带 shouldCompoentUpdate 的浅比拟优化
  • 联合 Immutable.js 达到最优
正文完
 0