关于前端:社招前端二面必会react面试题及答案

高阶组件的利用场景

权限管制

利用高阶组件的 条件渲染 个性能够对页面进行权限管制,权限管制个别分为两个维度:页面级别页面元素级别

// HOC.js    
function withAdminAuth(WrappedComponent) {    
    return class extends React.Component {    
        state = {    
            isAdmin: false,    
        }    
        async componentWillMount() {    
            const currentRole = await getCurrentUserRole();    
            this.setState({    
                isAdmin: currentRole === 'Admin',    
            });    
        }    
        render() {    
            if (this.state.isAdmin) {    
                return <WrappedComponent {...this.props} />;    
            } else {    
                return (<div>您没有权限查看该页面,请分割管理员!</div>);    
            }    
        }    
    };    
}

// 应用
// pages/page-a.js    
class PageA extends React.Component {    
    constructor(props) {    
        super(props);    
        // something here...    
    }    
    componentWillMount() {    
        // fetching data    
    }    
    render() {    
        // render page with data    
    }    
}    
export default withAdminAuth(PageA);    

可能你曾经发现了,高阶组件其实就是装璜器模式在 React 中的实现:通过给函数传入一个组件(函数或类)后在函数外部对该组件(函数或类)进行性能的加强(不批改传入参数的前提下),最初返回这个组件(函数或类),即容许向一个现有的组件增加新的性能,同时又不去批改该组件,属于 包装模式(Wrapper Pattern) 的一种。

什么是装璜者模式:在不扭转对象本身的前提下在程序运行期间动静的给对象增加一些额定的属性或行为

能够进步代码的复用性和灵活性

再对高阶组件进行一个小小的总结:

  • 高阶组件 不是组件 一个把某个组件转换成另一个组件的 函数
  • 高阶组件的次要作用是 代码复用
  • 高阶组件是 装璜器模式在 React 中的实现

封装组件的准则

封装准则

1、繁多准则:负责繁多的页面渲染

2、多重职责:负责多重职责,获取数据,复用逻辑,页面渲染等

3、明确承受参数:必选,非必选,参数尽量设置以_结尾,防止变量反复

4、可扩大:需要变动可能及时调整,不影响之前代码

5、代码逻辑清晰

6、封装的组件必须具备高性能,低耦合的个性

7、组件具备繁多职责:封装业务组件或者根底组件,如果不能给这个组件起一个有意义的名字,证实这个组件承当的职责可能不够繁多,须要持续抽组件,直到它能够是一个独立的组件即可

理解redux吗?

  • redux 是一个利用数据流框架,次要解决了组件之间状态共享问题,原理是集中式治理,次要有三个外围办法:action store reduce
  • 工作流程
    view 调用store的dispatch 承受action传入的store,reduce进行state操作

    view通过store提供的getState获取最新的数据

  • redux的长处:

    新增的state 对状态的治理更加明确

    流程更加标准,缩小手动编写代码,进步编码效率

  • redux的毛病:

    当数据更新是有时候组件不须要,也要从新绘制,影响效率

react hooks,它带来了那些便当

  • 代码逻辑聚合,逻辑复用
  • HOC嵌套天堂
  • 代替class

React 中通常应用 类定义 或者 函数定义 创立组件:

在类定义中,咱们能够应用到许多 React 个性,例如 state、 各种组件生命周期钩子等,然而在函数定义中,咱们却无能为力,因而 React 16.8 版本推出了一个新性能 (React Hooks),通过它,能够更好的在函数定义组件中应用 React 个性。

益处:

  1. 跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官网的底层 API,最为轻量,而且革新老本小,不会影响原来的组件层次结构和传说中的嵌套天堂;
  2. 类定义更为简单
  3. 不同的生命周期会使逻辑变得扩散且凌乱,不易保护和治理;
  • 时刻须要关注this的指向问题;
  • 代码复用代价高,高阶组件的应用常常会使整个组件树变得臃肿;
  • 状态与UI隔离: 正是因为 Hooks 的个性,状态逻辑会变成更小的粒度,并且极容易被形象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离。

留神:

  • 防止在 循环/条件判断/嵌套函数 中调用 hooks,保障调用程序的稳固;
  • 只有 函数定义组件 和 hooks 能够调用 hooks,防止在 类组件 或者 一般函数 中调用;
  • 不能在useEffect中应用useState,React 会报错提醒;
  • 类组件不会被替换或废除,不须要强制革新类组件,两种形式能并存;

重要钩子

  1. 状态钩子 (useState): 用于定义组件的 State,其到类定义中this.state的性能;
// useState 只承受一个参数: 初始状态
// 返回的是组件名和更改该组件对应的函数
const [flag, setFlag] = useState(true);
// 批改状态
setFlag(false)

// 下面的代码映射到类定义中:
this.state = {
    flag: true    
}
const flag = this.state.flag
const setFlag = (bool) => {
    this.setState({
        flag: bool,
    })
}
  1. 生命周期钩子 (useEffect):

类定义中有许多生命周期函数,而在 React Hooks 中也提供了一个相应的函数 (useEffect),这里能够看做componentDidMount、componentDidUpdate和componentWillUnmount的联合。

useEffect(callback, [source])承受两个参数

  • callback: 钩子回调函数;
  • source: 设置触发条件,仅当 source 产生扭转时才会触发;
  • useEffect钩子在没有传入[source]参数时,默认在每次 render 时都会优先调用上次保留的回调中返回的函数,后再从新调用回调;
useEffect(() => {
    // 组件挂载后执行事件绑定
    console.log('on')
    addEventListener()

    // 组件 update 时会执行事件解绑
    return () => {
        console.log('off')
        removeEventListener()
    }
}, [source]);


// 每次 source 产生扭转时,执行后果(以类定义的生命周期,便于大家了解):
// --- DidMount ---
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- WillUnmount --- 
// 'off'

通过第二个参数,咱们便可模拟出几个罕用的生命周期:

  • componentDidMount: 传入[]时,就只会在初始化时调用一次
const useMount = (fn) => useEffect(fn, [])
  • componentWillUnmount: 传入[],回调中的返回的函数也只会被最终执行一次
const useUnmount = (fn) => useEffect(() => fn, [])
  • mounted: 能够应用 useState 封装成一个高度可复用的 mounted 状态;
const useMounted = () => {
    const [mounted, setMounted] = useState(false);
    useEffect(() => {
        !mounted && setMounted(true);
        return () => setMounted(false);
    }, []);
    return mounted;
}
  • componentDidUpdate: useEffect每次均会执行,其实就是排除了 DidMount 后即可;
const mounted = useMounted() 
useEffect(() => {
    mounted && fn()
})
  1. 其它内置钩子:
  2. useContext: 获取 context 对象
  • useReducer: 相似于 Redux 思维的实现,但其并不足以代替 Redux,能够了解成一个组件外部的 redux:

    • 并不是长久化存储,会随着组件被销毁而销毁;
    • 属于组件外部,各个组件是互相隔离的,单纯用它并无奈共享数据;
    • 配合useContext`的全局性,能够实现一个轻量级的 Redux;(easy-peasy)
  • useCallback: 缓存回调函数,防止传入的回调每次都是新的函数实例而导致依赖组件从新渲染,具备性能优化的成果;
  • useMemo: 用于缓存传入的 props,防止依赖的组件每次都从新渲染;
  • useRef: 获取组件的实在节点;
  • useLayoutEffect

    • DOM更新同步钩子。用法与useEffect相似,只是区别于执行工夫点的不同
    • useEffect属于异步执行,并不会期待 DOM 真正渲染后执行,而useLayoutEffect则会真正渲染后才触发;
    • 能够获取更新后的 state;
  • 自定义钩子(useXxxxx): 基于 Hooks 能够援用其它 Hooks 这个个性,咱们能够编写自定义钩子,如下面的useMounted。又例如,咱们须要每个页面自定义题目:
function useTitle(title) {
  useEffect(
    () => {
      document.title = title;
    });
}

// 应用:
function Home() {
    const title = '我是首页'
    useTitle(title)

    return (
        <div>{title}</div>
    )
}

ref是一个函数又有什么益处?

  • 不便react销毁组件、从新渲染的时候去清空refs的货色,避免内存泄露

简述flux 思维

Flux 的最大特点,就是数据的”单向流动”。

  • 用户拜访 View
  • View收回用户的 Action
  • Dispatcher 收到Action,要求 Store 进行相应的更新
  • Store 更新后,收回一个"change"事件
  • View 收到"change"事件后,更新页面

React的虚构DOM和Diff算法的外部实现

传统 diff 算法的工夫复杂度是 O(n^3),这在前端 render 中是不可承受的。为了升高工夫复杂度,react 的 diff 算法做了一些斗争,放弃了最优解,最终将工夫复杂度升高到了 O(n)。

那么 react diff 算法做了哪些斗争呢?,参考如下:

  1. tree diff:只比照同一层的 dom 节点,疏忽 dom 节点的跨层级挪动

如下图,react 只会对雷同色彩方框内的 DOM 节点进行比拟,即同一个父节点下的所有子节点。当发现节点不存在时,则该节点及其子节点会被齐全删除掉,不会用于进一步的比拟。

这样只须要对树进行一次遍历,便能实现整个 DOM 树的比拟。

这就意味着,如果 dom 节点产生了跨层级挪动,react 会删除旧的节点,生成新的节点,而不会复用。

  1. component diff:如果不是同一类型的组件,会删除旧的组件,创立新的组件
  1. element diff:对于同一层级的一组子节点,须要通过惟一 id 进行来辨别
  2. 如果没有 id 来进行辨别,一旦有插入动作,会导致插入地位之后的列表全副从新渲染
  3. 这也是为什么渲染列表时为什么要应用惟一的 key。

参考 前端进阶面试题具体解答

key的作用

是给每一个 vnode 的惟一 id,能够依附 key,更精确,更快的拿到 oldVnode 中对应的 vnode 节点

<!-- 更新前 -->
<div>
  <p key="ka">ka</p>
  <h3 key="song">song</he>
</div>

<!-- 更新后 -->
<div>
  <h3 key="song">song</h3>
  <p key="ka">ka</p>
</div>

如果没有 key,React 会认为 div 的第一个子节点由 p 变成 h3,第二个子节点由 h3 变成 p,则会销毁这两个节点并从新结构。

然而当咱们用 key 指明了节点前后对应关系后,React 晓得 key === "ka" 的 p 更新后还在,所以能够复用该节点,只须要替换程序。

key 是 React 用来追踪哪些列表元素被批改、被增加或者被移除的辅助标记。

在开发过程中,咱们须要保障某个元素的 key 在其同级元素中具备唯一性。在 React diff 算法中,React 会借助元素的 Key 值来判断该元素是早先创立的还是被挪动而来的元素,从而缩小不必要的元素从新渲染。同时,React 还须要借助 key 来判断元素与本地状态的关联关系。

React 性能优化

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

react diff 算法

咱们晓得React会保护两个虚构DOM,那么是如何来比拟,如何来判断,做出最优的解呢?这就用到了diff算法

diff算法的作用

计算出Virtual DOM中真正变动的局部,并只针对该局部进行原生DOM操作,而非从新渲染整个页面。

传统diff算法

通过循环递归对节点进行顺次比照,算法复杂度达到 O(n^3) ,n是树的节点数,这个有多可怕呢?——如果要展现1000个节点,得执行上亿次比拟。。即使是CPU快能执行30亿条命令,也很难在一秒内计算出差别。

React的diff算法

  1. 什么是和谐?

将Virtual DOM树转换成actual DOM树的起码操作的过程 称为 和谐 。

  1. 什么是React diff算法?

diff算法是和谐的具体实现。

diff策略

React用 三大策略 将O(n^3)杂度 转化为 O(n)复杂度

策略一(tree diff):

  • Web UI中DOM节点跨层级的挪动操作特地少,能够忽略不计
  • 同级比拟,既然DOM 节点跨层级的挪动操作少到能够忽略不计,那么React通过updateDepth 对 Virtual DOM 树进行层级管制,也就是同一层,在比照的过程中,如果发现节点不在了,会齐全删除不会对其余中央进行比拟,这样只须要对树遍历一次就OK了

策略二(component diff):

  • 领有雷同类的两个组件 生成类似的树形构造,
  • 领有不同类的两个组件 生成不同的树形构造。

策略三(element diff):

对于同一层级的一组子节点,通过惟一id辨别。

tree diff

  • React通过updateDepth对Virtual DOM树进行层级管制。
  • 对树分层比拟,两棵树 只对同一档次节点 进行比拟。如果该节点不存在时,则该节点及其子节点会被齐全删除,不会再进一步比拟。
  • 只需遍历一次,就能实现整棵DOM树的比拟。

那么问题来了,如果DOM节点呈现了跨层级操作,diff会咋办呢?

答:diff只简略思考同层级的节点地位变换,如果是跨层级的话,只有创立节点和删除节点的操作。

如上图所示,以A为根节点的整棵树会被从新创立,而不是挪动,因而 官网倡议不要进行DOM节点跨层级操作,能够通过CSS暗藏、显示节点,而不是真正地移除、增加DOM节点

component diff

React对不同的组件间的比拟,有三种策略

  1. 同一类型的两个组件,按原策略(层级比拟)持续比拟Virtual DOM树即可。
  2. 同一类型的两个组件,组件A变动为组件B时,可能Virtual DOM没有任何变动,如果晓得这点(变换的过程中,Virtual DOM没有扭转),可节俭大量计算工夫,所以 用户 能够通过 shouldComponentUpdate() 来判断是否须要 判断计算。
  3. 不同类型的组件,将一个(将被扭转的)组件判断为dirty component(脏组件),从而替换 整个组件的所有节点。

留神:如果组件D和组件G的构造类似,然而 React判断是 不同类型的组件,则不会比拟其构造,而是删除 组件D及其子节点,创立组件G及其子节点。

element diff

当节点处于同一层级时,diff提供三种节点操作:删除、插入、挪动。

  • 插入:组件 C 不在汇合(A,B)中,须要插入
  • 删除:

    • 组件 D 在汇合(A,B,D)中,但 D的节点曾经更改,不能复用和更新,所以须要删除 旧的 D ,再创立新的。
    • 组件 D 之前在 汇合(A,B,D)中,但汇合变成新的汇合(A,B)了,D 就须要被删除。
  • 挪动:组件D曾经在汇合(A,B,C,D)里了,且汇合更新时,D没有产生更新,只是地位扭转,如新汇合(A,D,B,C),D在第二个,毋庸像传统diff,让旧汇合的第二个B和新汇合的第二个D 比拟,并且删除第二个地位的B,再在第二个地位插入D,而是 (对同一层级的同组子节点) 增加惟一key进行辨别,挪动即可。

diff的有余与待优化的中央

尽量减少相似将最初一个节点挪动到列表首部的操作,当节点数量过大或更新操作过于频繁时,会影响React的渲染性能

在 React 中,何为 state

State 和 props 相似,但它是公有的,并且齐全由组件本身管制。State 实质上是一个持有数据,并决定组件如何渲染的对象。

ssr原理是什么?

外围原理其实就是借助虚构DOM来实现react代码可能在服务器运行的,node外面能够执行react代码

传入 setState 函数的第二个参数的作用是什么?

该函数会在 setState 函数调用实现并且组件开始重渲染的时候被调用,咱们能够用该函数来监听渲染是否实现:

this.setState(
  { username: 'tylermcginnis33' },
  () => console.log('setState has finished and the component has re-rendered.')
)
this.setState((prevState, props) => {
  return {
    streak: prevState.streak + props.count
  }
})

类组件(Class component)和函数式组件(Functional component)之间有何不同

  • 类组件不仅容许你应用更多额定的性能,如组件本身的状态和生命周期钩子,也能使组件间接拜访 store 并维持状态
  • 当组件仅是接管 props,并将组件本身渲染到页面时,该组件就是一个 ‘无状态组件(stateless component)’,能够应用一个纯函数来创立这样的组件。这种组件也被称为哑组件(dumb components)或展现组件

useEffect和useLayoutEffect的区别

useEffect
基本上90%的状况下,都应该用这个,这个是在render完结后,你的callback函数执行,然而不会block browser painting,算是某种异步的形式吧,然而class的componentDidMount 和componentDidUpdate是同步的,在render完结后就运行,useEffect在大部分场景下都比class的形式性能更好.
useLayoutEffect
这个是用在解决DOM的时候,当你的useEffect外面的操作须要解决DOM,并且会扭转页面的款式,就须要用这个,否则可能会呈现呈现闪屏问题, useLayoutEffect外面的callback函数会在DOM更新实现后立刻执行,然而会在浏览器进行任何绘制之前运行实现,阻塞了浏览器的绘制.

何为 JSX

JSX 是 JavaScript 语法的一种语法扩大,并领有 JavaScript 的全副性能。JSX 生产 React “元素”,你能够将任何的 JavaScript 表达式封装在花括号里,而后将其嵌入到 JSX 中。在编译实现之后,JSX 表达式就变成了惯例的 JavaScript 对象,这意味着你能够在 if 语句和 for 循环外部应用 JSX,将它赋值给变量,承受它作为参数,并从函数中返回它。

在哪个生命周期中你会收回Ajax申请?为什么?

Ajax申请应该写在组件创立期的第五个阶段,即 componentDidMount生命周期办法中。起因如下。
在创立期的其余阶段,组件尚未渲染实现。而在存在期的5个阶段,又不能确保生命周期办法肯定会执行(如通过 shouldComponentUpdate办法优化更新等)。在销毀期,组件行将被销毁,申请数据变得无意义。因而在这些阶段发岀Ajax申请显然不是最好的抉择。
在组件尚未挂载之前,Ajax申请将无奈执行结束,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount办法中,执行Ajax即可保障组件曾经挂载,并且可能失常更新组件。

为什么React并不举荐优先思考应用Context?

  • Context目前还处于试验阶段,可能会在前面的发行版本中有很大的变动,事实上这种状况曾经产生了,所以为了防止给今后降级带来大的影响和麻烦,不倡议在app中应用context。
  • 只管不倡议在app中应用context,然而独有组件而言,因为影响范畴小于app,如果能够做到高内聚,不毁坏组件树之间的依赖关系,能够思考应用context
  • 对于组件之间的数据通信或者状态治理,无效应用props或者state解决,而后再思考应用第三方的成熟库进行解决,以上的办法都不是最佳的计划的时候,在思考context。
  • context的更新须要通过setState()触发,然而这并不是很牢靠的,Context反对跨组件的拜访,然而如果两头的子组件通过一些办法不影响更新,比方 shouldComponentUpdate() 返回false 那么不能保障Context的更新肯定能够应用Context的子组件,因而,Context的可靠性须要关注

react旧版生命周期函数

初始化阶段

  • getDefaultProps:获取实例的默认属性
  • getInitialState:获取每个实例的初始化状态
  • componentWillMount:组件行将被装载、渲染到页面上
  • render:组件在这里生成虚构的DOM节点
  • componentDidMount:组件真正在被装载之后

运行中状态

  • componentWillReceiveProps:组件将要接管到属性的时候调用
  • shouldComponentUpdate:组件承受到新属性或者新状态的时候(能够返回false,接收数据后不更新,阻止render调用,前面的函数不会被继续执行了)
  • componentWillUpdate:组件行将更新不能批改属性和状态
  • render:组件从新描述
  • componentDidUpdate:组件曾经更新

销毁阶段

  • componentWillUnmount:组件行将销毁

在 React 中如何处理事件

为了解决跨浏览器的兼容性问题,SyntheticEvent 实例将被传递给你的事件处理函数,SyntheticEvent是 React 跨浏览器的浏览器原生事件包装器,它还领有和浏览器原生事件雷同的接口,包含 stopPropagation()preventDefault()
比拟乏味的是,React 实际上并不将事件附加到子节点自身。React 应用单个事件侦听器侦听顶层的所有事件。这对性能有益处,也意味着 React 在更新 DOM 时不须要跟踪事件监听器。

展现组件(Presentational component)和容器组件(Container component)之间有何不同

展现组件关怀组件看起来是什么。展现专门通过 props 承受数据和回调,并且简直不会有本身的状态,但当展现组件领有本身的状态时,通常也只关怀 UI 状态而不是数据的状态。

容器组件则更关怀组件是如何运作的。容器组件会为展现组件或者其它容器组件提供数据和行为(behavior),它们会调用 Flux actions,并将其作为回调提供给展现组件。容器组件常常是有状态的,因为它们是(其它组件的)数据源。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理