一 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
为例:
- 所有调用
useState
方法的地方,都会**共享一个全局变量val
。 - 因此在组件中多次调用,就会引起变量冲突问题。
5.2 解决变量冲突问题
在上面,我们初步实现了useState和useEffect函数,并成功调用,但是上述案例都是在一个变量的情况下发生的,那么倘若有两个变量的情况下,依旧采用上述的自定义代码,会发生什么?
代码改进:
- 通过一个全局的数组来维护变量。
- 通过一个全局的下标用来定位对应的状态变量存储的位置。
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函数肯定是要更完善的。希望大家引以区分。
六 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 的区别是什么?各自有什么应用场景?
- MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式。
- 耦合性低
- 重用性高
- 生命周期成本低
-
MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。
-
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
函数会生成一个记忆函数,这样更新时就能保证这个函数不会发生渲染。 从而达到规避性能浪费的目标。