【React】系列(一)

一 React Class 组件中请求可以在 componentWillMount 中发起吗?为什么?

不可以,需要在 componentDidMount 钩子处理

看情况,如果是服务端渲染会拿不到数据。

componentWillMount方法的调用在constructor之后,在render之前,在这方法里的代码调用setState方法不会触发重渲染,所以它一般不会用来作加载数据之用,它也很少被使用到。

一般的从后台(服务器)获取的数据,都会与组件上要用的数据加载有关,所以都在componentDidMount方法里面作。虽然与组件上的数据无关的加载,也可以在constructor里作,但constructor是作组件state初绐化工作,并不是设计来作加载数据这工作的,所以所有有副作用的代码都会集中在componentDidMount方法里。

二 React Class 组件和 React Hook 的区别有哪些?

Hook代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护;

组件树层级变浅,在原本的代码中,我们经常使用 HOC/render/Props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实。

hooks组件其实是降低了react开发的使用难度的,让新手可以在不使用class组件的情况下依然可以进行项目开发。

Hooks的优点:

  • React Hooks 代码可读性更强。hooks 可以将class组件拆分在不同生命周期函数中的功能代码聚合,方便阅读维护;
  • hooks组件开发难度更底

三 React中高阶函数和自定义hooks的优缺点?【?? hooks功能复用更方便】

高阶组件实际上就是把一个组件当参数传入,再返回一个新的组件出来。业务过度封装的高阶组件,可能会导致组件层次嵌套变深。

而自定义 Hook 可以不用使用高阶组件依然可以进行功能复用。

四 简要说明 React Hook 中 useState 的运行原理?

useState返回一个有状态值和一个函数来更新它。在初始渲染期间,返回的状态(状态)与作为第一个参数(initialState)传递的值相同。setState 函数用于更新状态。它接受一个新的状态值,并排队等待重新渲染该组件。 在更新过程中,

  • 首次渲染,render()
  • render会调用App函数,得到虚拟DIV,创建真实DIV
  • 用户点击Button,调用setN(n+1),render函数被再一次调用
  • render进一步调用App函数,得到虚拟DIV,Diff,更新真实DIV
  • 每一次setN都会再次调用render,进而调用App

五 useState 与 useEffect 的实现思路

5.1 useState的实现 与 useEffect的实现 【有问题的版本】
import React from "react";
import ReactDOM from "react-dom";

let val;
function useState(initVal) {
  val = val || initVal;
  function setVal(newVal) {
    val = newVal;
    render(); // 重新render页面
  }
  return [val, setVal];
}

复制代码

// 自定义的useEffect
let watchArr;
function useEffect(fn, watch) {
  const hasWactchChange = watchArr
    ? !watch.every((val, i) => val === watchArr[i])
    : true;
  if (hasWactchChange) {
    fn();
    watchArr = watch;
  }
}

复制代码

变量冲突原因分析:以useState为例:

  1. 所有调用useState方法的地方,都会**共享一个全局变量val
  2. 因此在组件中多次调用,就会引起变量冲突问题。
5.2 解决变量冲突问题

在上面,我们初步实现了useState和useEffect函数,并成功调用,但是上述案例都是在一个变量的情况下发生的,那么倘若有两个变量的情况下,依旧采用上述的自定义代码,会发生什么?

代码改进:

  1. 通过一个全局的数组来维护变量。
  2. 通过一个全局的下标用来定位对应的状态变量存储的位置。
import React from "react";
import ReactDOM from "react-dom";

let memoizedState = [];
let currentIndex = 0;

function useState(initVal) {
  memoizedState[currentIndex] = memoizedState[currentIndex] || initVal;
  const cursor = currentIndex;
  function setVal(newVal) {
    memoizedState[cursor] = newVal;
    render();
  }
  // 返回state 然后 currentIndex+1
  return [memoizedState[currentIndex++], setVal];
}

// 自定义的useEffect
function useEffect(fn, watch) {
  const hasWatchChange = memoizedState[currentIndex]
    ? !watch.every((val, i) => val === memoizedState[currentIndex][i])
    : true;
  if (hasWatchChange) {
    fn();
    memoizedState[currentIndex] = watch;
    currentIndex++; // 累加 currentIndex
  }
}

function App() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(0);
  useEffect(() => {
    console.log(`自定义useEffect调用--count:${count}`);
  }, [count]);

  useEffect(() => {
    console.log(`自定义useEffect调用--data:${data}`);
  }, [data]);

  return (
    <div>
      <button onClick={() => { setCount(count + 1); }}>
        {`按钮1:当前点击次数:${count}`}
      </button>

      <hr />
      <button onClick={() => { setData(data + 1); }}>
        {`按钮2:当前点击次数:${data}`}
      </button>
    </div>
  )
}

// 初次渲染用
render();
function render() {
  console.log(memoizedState); // 执行hook后 数组的变化
  currentIndex = 0; // 重新render时需要设置为 0
  ReactDOM.render(<App />, document.getElementById("root"));
}


复制代码

从上述代码中,我们可以发现每次调用render()函数,都要将对应的全局下标重置为0,这个操作我刚开始看到就觉得匪夷所思,想了半天我才明白是为什么:

因为我们是根据Hook的调用顺序,来依次将变量存储在数组中的。

而我们useEffect函数的第二个参数是数组的原因,也是因为我们变量的存储也是以数组形式来存在。

备注:
我们这里的代码是个简化版的,官方的useState和useEffect函数肯定是要更完善的。希望大家引以区分。

image.png

六 React绑定事件时,使用 useCallback 来做性能优化

一个React组件中,比如绑定了一个onClick={fn}的事件,如果将fn定义成普通函数,那么,在每次render的时候都会生成一个新的fn函数,造成资源浪费。

在日常开发当中,我们往往会给一个按钮添加一个onChange事件或者onClick事件,那么就以onClick事件为例:

  • 假设按钮A绑定了onClick事件,里面肯定是调用我们自定义的onClick函数。
  • 由于其他组件的原因我们触发了组件的渲染,那么每次render的时候,都会重新产生新的onClick函数。
  • 这会造成不必要的渲染而引起性能浪费。

伪代码如下:

class Demo extends Component{
    render() {
        return 
        <div>
            <Button onClick={ () => { console.log('Hello World!!'); }}  />
        </div>;
    }
}
复制代码

因此我们在类式组件开发过程中,往往会这么改写代码,来避免性能浪费问题:

class Demo extends Component{
    constructor(){
        super();
        this.buttonClick = this.buttonClick.bind(this);
    }
    render() {
        return 
        <div>
            <Button onClick={ this.buttonClick }  />
        </div>;
    }
}
复制代码

那如果在函数式组件中开发,如何写呢?

回答:采用ReactHook中的useCallback()函数:

function Demo(){
    const buttonClick = useCallback(() => { 
    	console.log('Hello World!!')
    },[])
    return(
        <div>
            <Button onClick={ buttonClick }  />
        </div>
    )
}

复制代码

七 useEffect (副作用Hook) 说明 与 使用

【使用useEffect来隔离副作用】

该 Hook 接收一个包含命令式、且可能有副作用代码的函数。

在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。(官方文档)

【我们还会在useEffect中发起请求】

TIPS:

你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

如何使用useEffect

7.1 【 只在初始化时,调用一次 】实现componentDidMount 的功能,(第二个参数为[])

useEffect的第二个参数为一个空数组,初始化调用一次之后不再执行,相当于componentDidMount。

7.2 【 组件的初始化和更新都会执行 】 实现组合 componentDidMount componentDidUpdate 的功能,(没有第二个参数)

当useEffect没有第二个参数时,组件的初始化和更新都会执行。

7.3 实现组合 componentDidMount componentWillUnmount 的功能

useEffect返回一个函数,这个函数会在组件卸载时执行

useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    return () => clearInterval(id);
}, []);
复制代码

八 React 如何发现重渲染,什么原因造成重渲染,如何避免重渲染?

答: 当内部data发生改变,state发生改变(通过调用this.setState()) 以及父组件传过来的props发生改变时,会导致组件重新渲染。

react生命周期中有这样一个钩子,叫shouldComponentUpdate函数,是重渲染时render()函数调用前被调用的函数,两个参数 nextProps和nextState ,分别表示下一个props和state的值。当函数返回false时,阻止接下来的render()函数的调用,阻止组件重渲染,返回true时,组件照常渲染。 前后不改变state的值的setState和无数据交换的父组件的重渲染都会导致组件的重渲染,但我们可以通过shouldComponentUpdate来阻止这两种情况,shouldComponentUpdate并不是完美的,只能阻止扁平的对象,这时候可以考虑Immutable.js(Immutable.js 的基本原则是对于不变的对象返回相同的引用,而对于变化的对象,返回新的引用)或者PureRenderMixin 插件。

九 React 中 useState 是如何做数据初始化的?

答: 一个函数组件,在react执行渲染时该函数都会被调用,所以函数内的useState在每次都会被调用。useState在不同阶段,其对应的实现不一样,在onMount阶段:初始化state;在onUpdate阶段:更新state。useState返回的是一个数组,数组的第二项是一个函数,该函数每次被调用后,都会触发react的更新

十 ##### 列举你常用的 React 性能优化技巧?

答:

  • 使用 shouldComponentUpdate 规避冗余的更新逻辑
  • PureComponent + Immutable.js
  • React.memo 与 useMemo

十一 简要说明 Vue 2.x 的全链路运作机制?

答:

  • 初始化以及挂载init, mount
  • 在进行模板编译compile,将template编译为渲染函数render function
  • 执行render function生成Virtual DOM, render function => VNode tree
  • 再进行响应式依赖收集,render function => getter, setter => Watcher.update => patch。以及使用队列进行异步更新的策略。
  • 最后通过diff算法后进行patch更新视图

十二 Vue 里实现跨组件通信的方式有哪些?

答:

  • 父子通信:

父向子传递数据是通过 props,子向父是通过 events(emit);通过父链/子链也可以通信(emit);通过父链 / 子链也可以通信(emit);通过父链/子链也可以通信(parent / children);ref也可以访问组件实例;provide/injectAPI;children);ref 也可以访问组件实例;provide / inject API;children);ref也可以访问组件实例;provide/injectAPI;attrs/$listeners

  • 兄弟通信:

Bus;Vuex

  • 跨级通信:

Bus;Vuex;provide / inject API、attrs/attrs/attrs/listeners

十三 Vue 中响应式数据是如何做到对某个对象的深层次属性的监听的?

答:

使用watch并且搭配deep:true 就可以实现对对象的深度监听

十四 MVVM、MVC 和 MVP 的区别是什么?各自有什么应用场景?

  1. MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式。
  • 耦合性低
  • 重用性高
  • 生命周期成本低
  1. MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。

  2. MVVM 本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

  • 低耦合
  • 可重用性
  • 独立开发
  • 可测试

MVVM,特点是采用双向绑定(data-binding): View的 变动,自动反映在View Model,反之亦然。这样开发者就不用处理接收事件和View更新的工作,框架已经帮你做好了。

十五 Webpack 中 Loader 和 Plugin 的区别是什么?

答:

在webpack中 Loader 就是负责完成项目中各种各样资源模块的加载,从而实现整体项目的模块化,而 Plugin 则是用来解决项目中除了资源模块打包以外的其他自动化工作,对比 Loader 只是在模块的加载环节工作,而插件的作用范围几乎可以触及 Webpack 工作的每一个环节。

十六 Git Hook 在项目中哪些作用?

答:

Git Hooks是定制化的脚本程序,所以它实现的功能与相应的git动作相关,如下几个简单例子:

  • 多人开发代码语法、规范强制统一
  • commit message 格式化、是否符合某种规范
  • 如果有需要,测试用例的检测
  • 服务器代码有新的更新的时候通知所有开发成员
  • 代码提交后的项目自动打包(git receive之后)
  • 等等…

十七 ##### HTTPS 相比 HTTP 为什么更加安全可靠?

答:

因为 HTTPS 保证了传输安全,防止传输过程被监听、防止数据被窃取,可以确认网站的真实性(具体细节二面再说)。不过需要注意的是,即便使用 HTTPS 仍可能会被抓包,因为HTTPS 只防止用户在不知情的情况下通信被监听,如果用户主动授信,是可以构建“中间人”网络,代理软件可以对传输内容进行解密

十八

十九

二十

参考

总结

  • 在hooks组件中 绑定事件 建议用 useCallback 来写事件回调,注意依赖数组要为 空数组[]
  • 作用:useCallback函数会生成一个记忆函数,这样更新时就能保证这个函数不会发生渲染。  从而达到规避性能浪费的目标。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享