小纪

777 阅读16分钟

1、react hooks

Q: 用过useMemo么?

A:  useState,返回一个 state,以及更新 state 的函数。

      useEffect,用来处理副作用

      useContext和consumer类似,仍然需要与Provider配合使用

      useReducer用于管理复杂结构的状态对象,与redux的核心逻辑一致。

      useCallbackuseCallback返回一个memoized函数,在参数a和b都没有改变时,总是返回同一个函数。

      useMemo返回一个memoized函数执行结果。

      useRef类似 react.createRef()。

      在使用hooks的function component中,useRef不仅可以用来做DOM的引用,还可以做来作为类似class的实例属性,因为相同位置的useRef()每次返回的都是同一个对象。

      useImperativeHandle可以让你在使用 ref 时自定义暴露给父组件的实例值

      useLayoutEffect其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。

      useDebugValue可用于在 React 开发者工具中显示自定义 hook 的标签。

Q: react hooks优化了什么?

A: 在组件之间复用状态逻辑很难; 复杂组件变得难以理解; 难以理解的 class

Q: useEffect怎么只执行一次,相当于componentDidMount效果?

A: 第二个参数是空数组,因为依赖一直不变化,callback 不会二次执行。

Q: 为什么不能放在if中使用?

A:  memoizedState 数组是按 hook定义的顺序来放置数据的,如果 hook 顺序变化,memoizedState 并不会感知到。

代码关键在于:

  1. 初次渲染的时候,按照 useState,useEffect 的顺序,把 state,deps 等按顺序塞到 memoizedState 数组中。
  2. 更新的时候,按照顺序,从 memoizedState 中把上次记录的值拿出来。
真正的 React 实现
  • React 中是通过类似单链表的形式来代替数组的。通过 next 按顺序串联所有的 hook。
  • memoizedState,cursor 是存在哪里的?如何和每个函数组件一一对应的?

        react 会生成一棵组件树(或Fiber 单链表),树中每个节点对应了一个组件,hooks 的数据就作为组件的一个信息,存储在这些节点上,伴随组件一起出生,一起死亡。

Q: 自定义的 Hook 是如何影响使用它的函数组件的?

A: 共享同一个 memoizedState,共享同一个顺序。

Q: “Capture Value” 特性是如何产生的?

A: 每一次 ReRender 的时候,都是重新去执行函数组件了,对于之前已经执行过的函数组件,并不会做任何操作。

可以使用ref来在setInterval获取最新的值

拓展

useEffect和useLayoutEffect区别

useEffect

基本上90%的情况下,都应该用这个,这个是在render结束后,你的callback函数执行,但是不会block browser painting,算是某种异步的方式吧,但是class的componentDidMount 和componentDidUpdate是同步的,在render结束后就运行,useEffect在大部分场景下都比class的方式性能更好

useLayoutEffect

这个是用在处理DOM的时候,当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.

useMemo、useCallback、useEffect区别

useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。

useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。

useMemo(() => fn, inputs) 等价于 useCallback(fn, inputs)

useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。

模拟整个生命周期中只运行一次的方法

useMemo(() => {
  // execute only once
}, []);

模拟shouldComponentUpdate

const areEqual = (prevProps, nextProps) => {
   // 返回结果和shouldComponentUpdate正好相反
   // 访问不了state
}; 
React.memo(Foo, areEqual);

模拟componentDidMount

useEffect(() => {
    // 这里在mount时执行一次
}, []);

模拟componentDidUpdate

const mounted = useRef();
useEffect(() => {
  if (!mounted.current) {
    mounted.current = true;
  } else {
    // 这里只在update是执行
  }
});

模拟componentDidUnmount

useEffect(() => {
    // 这里在mount时执行一次
    return () => {
       // 这里在unmount时执行一次
    }
}, []);

github.com/brickspert/…

2、react16

Q:16有哪些静态方法?错误区域?

A:生命周期:

挂载

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

更新

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

卸载

当组件从 DOM 中移除时会调用如下方法:

错误处理

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

static getDerivedStateFromProps(props, state)

getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。这个函数是一个 static 函数,也是一个纯函数,里面不能通过 this 访问到当前组件(强制避免一些有副作用的操作),输入只能通过参数,对组件渲染的影响只能通过返回值。目的大概也是让开发者逐步去适应异步渲染。

getSnapshotBeforeUpdate(prevProps, prevState)

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

static getDerivedStateFromError(error)

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

componentDidCatch(error, info)

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

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

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

componentDidCatch 和 getDerivedStateFromError 的 区别

我们之前说了两个阶段, render phasecommit phase.

render phase 里产生异常的时候, 会调用 getDerivedStateFromError;

在 commit phase 里产生异常大的时候, 会调用 componentDidCatch

严格来说, 其实还有一点区别:

componentDidCatch 是不会在服务器端渲染的时候被调用的 而 getDerivedStateFromError 会。

segmentfault.com/a/119000001…

背景小结

什么是Fiber: ‘ 哦, 这个这个东西是支持异步渲染的, 虽然这个东西还没开启’。

然后就是渲染的两个阶段:renderphasecommit phase.

  • render phase 可以被打断, 大家不要在此阶段做一些有副作用的操作,可以放心在commit phase 里做。
  • 然后就是生命周期的调整, react 把你有可能在render phase 里做的有副作用的函数都改成了static 函数, 强迫开发者做一些纯函数的操作。


拓展

Suspense要解决的两个问题:

  1. 代码分片;
  2. 异步获取数据。

代码分片:

import React from "react";
import moment from "moment";
 
const Clock = () => <h1>{moment().format("MMMM Do YYYY, h:mm:ss a")}</h1>;

export default Clock;
// Usage of Clock
const Clock = React.lazy(() => {
  console.log("start importing Clock");
  return import("./Clock");
});
<Suspense fallback={<Loading />}>
  { showClock ? <Clock/> : null}
</Suspense>

showClock 为 true, 就尝试render clock, 这时候, 就触发另一个事件: 去加载clock.js 和它里面的 lib momment。

看到这你可能觉得奇怪, 怎么还需要用个<Suspense> 包起来, 有啥用, 不包行不行。

哎嗨, 不包还真是不行。 为什么呢?

前面我们说到, 目前react 的渲染模式还是同步的, 一口气走到黑, 那我现在画到clock 这里, 但是这clock 在另外一个文件里, 服务器就需要去下载, 什么时候能下载完呢, 不知道。 假设你要花十分钟去下载, 那这十分钟你让react 去干啥, 总不能一直等你吧。 Suspens 就是来解决这个问题的, 你要画clock, 现在没有,那就会抛一个异常出来,我们之前说
componentDidCatch 和 getDerivedStateFromProps, 这两个函数就是来抓子组件 或者 子子组件抛出的异常的。

子组件有异常的时候就会往上抛,直到某个组件的 getDerivedStateFromProps 抓住这个异常,抓住之后干嘛呢, 还能干嘛呀, 忍着。 下载资源的时候会抛出一个promise, 会有地方(这里是suspense)捕捉这个promise, suspense 实现了getDerivedStateFromProps, getDerivedStateFromProps 捕获到异常的时候, 一看, 哎, 小老弟,你来啦,还是个promise, 然后就等这个promise resole, resolve 完成之后呢,它会尝试重新画一下子组件。这时候资源已经到本地了, 也就能画成功了。

用伪代码 大致实现一下:

getDerivedStateFromError(error) {
   if (isPromise(error)) {
      error.then(reRender);
   }
}

异步获取数据:这一部分功能还没正式发布

Q: PureComponent与Component区别?

A: React.PureComponentReact.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。

拓展

React.createRef

React.createRef 创建一个能够通过 ref 属性附加到 React 元素的 ref

class MyComponent extends React.Component {
  constructor(props) {
    super(props);

    this.inputRef = React.createRef();
  }

  render() {
    return <input type="text" ref={this.inputRef} />;
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }
}

React.forwardRef

React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:

React.forwardRef 接受渲染函数作为参数。React 将使用 propsref 作为参数来调用此函数。此函数应返回 React 节点。

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

React.lazy

React.lazy() 允许你定义一个动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。

React.Suspense

React.Suspense 可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。目前,懒加载组件是 <React.Suspense> 支持的唯一用例:

3、react原理

Q:合成事件,虚拟dom?虚拟dom如何运行?diff算法比较什么?

A: 在 React 底层,主要对合成事件做了两件事:事件委派和自动绑定。

     2-1.事件委派在使用
所有事件绑定到结构的最外层,使用一个统一的事件监听器

     2-2.自动绑定
 自动绑定this为当前组件

React 阻止冒泡总结

(1)阻止合成事件间的冒泡,用e.stopPropagation();
(2)阻止合成事件与最外层document上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation();
(3)阻止合成事件与除最外层document上的原生事件上的冒泡,通过判断e.target来避免,代码如下:

componentDidMount() { 
document.body.addEventListener('click', e => {   
 if (e.target && e.target.matches('div.code')) {  
      return;    
  }    
  this.setState({   active: false,    });   }); 
 }

react事件注册

1. 一切从createInstance,创建dom实例开始讲起。

划重点了! 这两个方法等于将真实dom和fiber,props直接关联到了一起,相互引用,这点很重要,后续会用到,相当于前期准备。

2. listenTo

listenTo做的事情很简单,就是遍历props中的event,然后将事件和事件的依赖事件统统挂载到document上,并且所有的事件的回调函数走的都是dispatchEvent。

整个事件注册差不多就到这里为止了。react会把所有的事件都挂载到document上。这也是为什么我们event.stoppropagation()为什么不能阻止原生冒泡。因为所有事件都是绑定在document上的。意味着你的原生事件都执行完了之后,才能执行document的事件。dispatchEvent会做统一的派发。可以说原生事件的执行顺序是早于react事件的。

react事件合成

划重点 : 事件合成的过程。首先根据触发事件的target得到inst,然后遍历他的所有父节点(fiber.return属性),存储在局部遍历path中,记住这个path是有顺序关系的(后面可以解释react事件是如何阻止冒泡的)。得到path后进行遍历,假设遍历的组件同样注册了对应事件的listener,那么就挂载到event的_dispatchListeners和_dispatchInstances中去,这两个属性至关重要,后续的事件派发就是根据这两个属性进行的。 注意只有注册了对应事件的listener,才会挂载到event里面去。比如刚刚我们的ABC都绑定了Click,自然都会push到_dispatchListeners中去。

回忆下上面刚刚说到的一个很绕的地方。 假设我给一个input绑定了onChange事件,那么react会绑定很多依赖事件到document上。其中就有input事件。 但当我们触发input的时候,react是怎么触发到onChange的listener呢? 。重点就是刚刚说了三遍的地方,在合成input事件的时候,react会生成两个event,一个是input,一个是change,也就是说change的那个event挂载的_dispatchListeners里面存储了我们的listener。后续派发的时候,会执行这个事件队列,对 队列里的event进行派发。 这也就很好的解释了为什么我们的change交互和input一模一样。

事件派发

派发的过程实际上就是遍历事件队列的过程。遍历过程中拿到inst和对应的listener后,创建了个虚假的dom,绑定个自定义事件,然后再自己监听,再自己createEvent,再dispatch,触发callback,然后在callback里面触发真正的listener,同时会把合成的event传递进去。

segmentfault.com/a/119000001…

virtul DOM 也就是虚拟节点。通过JS的Object对象模拟DOM中的真实节点对象,再通过特定的render方法将其渲染成真实的DOM节点。

生成vNode---->渲染成真实节点 --------->挂载到页面--------->diff比较

Diff算法解析

不同节点类型的比较

分为两种情况:
(1)节点类型不同 
(2)节点类型相同,但是属性不同

可以看到,对于列表节点提供唯一的key属性可以帮助React定位到正确的节点进行比较,从而大幅减少DOM操作次数,提高了性能。

Q:setState是同步还是异步?为什么同步?

A:setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。 

 合成事件:就是react 在组件中的onClick等都是属于它自定义的合成事件

 原生事件:比如通过addeventListener添加的,dom中的原生事件

 setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。  

setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

4、Node

Q: Node如何区分环境?

A:通过环境变量来区分环境:process.env

拓展

Node环境与浏览器区别

一、全局环境下this的指向

  在node中this指向global而在浏览器中this指向window

二、js引擎

  在浏览器中不同的浏览器厂商提供了不同的浏览器内核,但是考虑到不同内核的少量差异,我们需要考虑浏览器兼容性。

  NodeJS是基于Chrome's JavaScript runtime,也就是说,实际上它是对GoogleV8引擎进行了封装。V8引 擎执行Javascript的速度非常快,性能非常好。

Node对一些特殊用例进行了优化,提供了替代的API,使得V8在非浏览器环境下运行得更好。例如,在服务器环境中,处理二进制数据通常是必不可少的,但Javascript对此支持不足,因此,V8.Node增加了Buffer类,方便并且高效地 处理二进制数据。因此,Node不仅仅简单的使用了V8,还对其进行了优化,使其在各环境下更加给力。

三、DOM操作

  浏览器中的js大多数情况下是在直接或间接(一些虚拟DOM的库和框架)的操作DOM。因为浏览器中的代码主要是在表现层工作。但是node是一门服务端技术。没有一个前台页面,所以我门不会在node中操作DOM。

四、I/O读写

  与浏览器不同,我们需要像其他服务端技术一样读写文件,nodejs提供了比较方便的组件。而浏览器(确保兼容性的)想在页面中直接打开一个本地的图片就麻烦了好多,而这一切node都用一个组件搞定了。

五、模块加载

  javascript有个特点,就是原生没提供包引用的API一次性把要加载的东西全执行一遍

  在nodeJS中提供了CMD的模块加载的AP。

  node还提供了npm 这种包管理工具,能更有效方便的管理我们饮用的库

5、webpack

Q:webpack用过什么?如何提高打包速度?


Q:bigfish有哪些优缺点?

A: Bigfish 企业级前端开发框架,解决了前端技术栈整合配置麻烦、开发联调麻烦、前端资源发布上线麻烦三大痛点。

Bigfish主要具备以下功能: 

 基于 umi 整合了前端工具链:整合了 Ant Design、Dva、Ant Design Pro 技术栈,最小化成本开发 

简化开发联调:提供前端 http 接口的代理,在开发过程中随时切换 mock 和联调接口,方便自测和联调 

 一站式发布部署:从工程初始化,到研发迭代管理,到一键发布静态资源及页面渲染