乐趣区

关于前端:社招前端二面必会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,)承受两个参数

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

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


// 每次 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,并将其作为回调提供给展现组件。容器组件常常是有状态的,因为它们是(其它组件的) 数据源。

退出移动版