Open CodingMeUp opened 6 years ago
React 下一代调和算法 Fiber 会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 这个生命周期函数的调用次数会变得不确定,React 可能会多次频繁调用 componentWillMount。如果我们将 AJAX 请求放到 componentWillMount 函数中,那么显而易见其会被触发多次,自然也就不是好的选择。
如果我们将 AJAX 请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount 函数中进行 AJAX 请求则能有效避免这个问题。
单一职责(Single responsibility principle),React组件设计推崇的是组合,而非继承。
如你的页面需要一个表单组件,表单中需要有输入框,按钮,列表,单选框等。那么在开发中你不应该只开发一(整)个表单组件((Form)),而是应该开发若干个单一功能的组件,比如输入框、提交按钮
你需要一个函数异步请求数据并返回JSON数据格式,那么你应该拆分为两个函数,一个复杂数据请求,另一个负责数据转化。你可能会好奇为什么一个简单的JSON.parse也拆分出来,因为将来需要会变动,你可能不仅仅需要JSON.parse,还需要转义,需要转化为proto buffer数据格式。而拆分之后如果再面临修改的话,就不会影响到数据请求部分的代码。
同样适用于开放/封闭(Open/closed principle)原则。开放/封闭强调的是对修改封闭(禁止修改内部代码),对拓展开放(允许你拓展功能)。因为修改意味着风险,可能会影响到不用修改的代码, 同时意味着暴露细节。你一定纳闷如果不允许修改代码的话如何拓展功能呢,在传统的面向对象编程中,这样的需求是通过继承和接口机制来实现的。在React中我们使用官方推荐的 Higher-Order Components 的模式去实现。
接口隔离(Interface segregation principle) 这个就放之四海而皆准了。第三方类库或者模块都避免不了对外提供调用接口,比如对于jQuery来说$是选择器,css用于设置样式,animate负责动画,你不希望把这三个接口都合并成一个叫做together吧,虽然实现起来没有问题,但是对于你将来维护这个类库,以及使用者调用类库,以及调用者的接替者阅读代码(因为他要区分不同上下文中调用这个接口究竟是用来干嘛的),都是不小的困难。
依赖反转(Inversion Of Control)原则。这条原则听上去有点拗口。这条原则是意思是,当你在为一个框架编写模块或者组件时,你只需要负责实现接口,并且到注册到框架里即可,然后等待框架来调用你,所以它的另另一个别名是 “Don't call us, we'll call you”。
这么说你可能没什么太大感觉,也不明白和“依赖”和“反转”有什么关系,说到底其实是一个控制权的问题。常规情况下当你在用express编写一个server时,代码是这样的:
const app = express();
module.exports = function (app) {
app.get('/newRoute', function(req, res) {...})
};
这意味着你正在编写的这个模块负责了/newRoute这个路径的处理,这个模块在掌握着主动权。 而用依赖反转的写法是:
module.exports = function plugin() {
return {
method: 'get',
route: '/newRoute',
handler: function(req, res) {...}
}
}
意味着你把控制权交给了引用这个模块的框架,这样的对比就体现了控制权的反转。 其实前端编程中常常用到这个原则,注入依赖就是对这个思维的体现。比如requireJS和Angular1.0中对依赖模块的引用使用的都是注入依赖的思想。
第 29 题:聊聊 Vue 的双向数据绑定,Model 如何改变 View,View 又是如何改变 Model 的
利用Proxy实现一个简化版的MVVM
参照vue的响应式设计模式,将数据劫持部分的Obejct.defineProperty
替换为Proxy
即可,其他部分,如compile(编译器没有实现,用写好的html模拟已完成编译),watcher,dep,事件监听等基本保持不变,简单实现代码如下:
<!-- html部分 -->
<div id="foo"></div>
<input type="text" name="" id="bar"/>
// js部分
class Watcher{
constructor(cb){
this.cb = cb;
}
update(){
this.cb()
}
}
class Dep{
constructor(){
this.subs = [];
}
publish(){
this.subs.forEach((item)=>{
item.update && item.update();
})
}
}
class MVVM{
constructor(data){
let that = this;
this.dep = new Dep();
this.data = new Proxy(data,{
get(obj, key, prox){
that.dep.target && that.dep.subs.push(that.dep.target);
return obj[key]
},
set(obj, key, value, prox){
obj[key] = value;
that.dep.publish();
return true;
}
})
this.compile();
}
compile(){
let divWatcher = new Watcher(()=>{
this.compileUtils().div();
})
this.dep.target = divWatcher;
this.compileUtils().div();
this.dep.target = null;
let inputWatcher = new Watcher(()=>{
this.compileUtils().input();
})
this.dep.target = inputWatcher;
this.compileUtils().input();
this.compileUtils().addListener();
this.dep.target = null;
}
compileUtils(){
let that = this;
return {
div(){
document.getElementById('foo').innerHTML = that.data.foo;
},
input(){
document.getElementById('bar').value = that.data.bar;
},
addListener(){
document.getElementById('bar').addEventListener('input', function(){
that.data.bar = this.value;
})
}
}
}
}
let mvvm = new MVVM({foo: 'foo233', bar: 'bar233'})
通过mvvm.data.foo
或者mvvm.data.bar
可以操作数据,可以观察到view做出了改变;在输入框改变输入值,也可以通过mvvm.data
观察到数据被触发改变
Vue React 生命周期
第 32 题:Virtual DOM 真的比操作原生 DOM 快吗?谈谈你的想法
不要天真地以为 Virtual DOM 就是快,diff 不是免费的,batching 么 MVVM 也能做,而且最终 patch 的时候还不是要用原生 API。在我看来 Virtual DOM 真正的价值从来都不是性能,而是它
调用 setState 之后发生了什么?
回答1
回答2
所谓的同步异步, 更准确的说法是是否进行了批处理。 对于React 17来说,批处理是通过标识位来实现的,在合成事件中以及hook中setState方法都是进行了批处理的;由于batchUpdate方法本身是同步的,因此setTimeout会导致标识位的设定不符合预期,从而出现批处理失败(转为同步)的情况;在SetTimeout中我们可以用手动批处理的方式实现批处理(ReactDOM.unstable_batchedUpdates);因此,React 17的批处理又可以称为半自动批处理。
对于React 18来说,批处理是通过优先级,以及优先级调度来实现的,因此在原生事件和setTimeout中都可以进行批处理。因此,React 17的批处理又可以称为自动批处理。当然,我们也可以使用flushSync的方法将异步转化为同步处理当前调度~
React 17采用标识位isBatchUpdate来判断是否进行批量更新
(1)将标识位isBatchUpdate置为true (2)将合成事件重所有的setState状态存储到一个quque中 (3)合成事件执行结束设置标识位isBatchUpdate置为false,并恢复之前的queue (4)最后统一获取queue中的数据,进行update
React 18通过优先级lane进行批量更新
批处理是 React 将多个状态更新分组到一个重新渲染中以获得更好的性能。如果没有自动批处理,我们只能在 React 事件处理程序中批处理更新。默认情况下,Promise、setTimeout、原生事件处理程序或任何其他事件内部的更新不会在 React 中批处理。使用自动批处理,这些更新将自动批处理, 四次都是异步的都是批处理的, 提供了flushSync方法, 其本质就是把传入的任务设置为高优先级,把当前处理的调度优先级改为该update的优先级,可以实现同步,有了含有优先级的update对象,并被挂在到fiber上后,就要开始我们的调度了,这也是react 18实现自动批处理的关键