赞
踩
完整版有编译器,运行时没有编译器。
响应式主要做三件事:
vue是通过数据劫持和发布订阅者模式来实现双向数据绑定的
就是利用oberver来监听module数据的变化,通过compile来解析模板,最终通过watcher来建立oberver和module之间的通信桥梁
创建一个vue对象,分为el模板和data数据
<body>
name: <span id="spanName"></span>
<br>
<input type="text" id="inputName">
</body>
<script>
var span = document.getElementById('spanName')
var input = document.getElementById('inputName')
var obj = {
name: ''
}
使用proxy数据劫持
obj = new Proxy(obj, {
get(target, prop) {
return target[prop]
},
set(target, prop, value) {
target[prop] = value
observer() //当数据有更新,改变页面的内容
}
})
function observer() {
span.innerHTML = obj.name
input.value = obj.name
}
input.oninput = function () {
obj.name = input.value //页面上的输入有变化,改变obj的数据
}
</script>
new Vue的时候
首先会初始化方法 ,调用自身的_init方法,
vm. m o u n t ( v m . mount(vm. mount(vm.options.el);方法主要:
mountComponent(this, el, hydrating)方法主要
使用Object.defineProperty() 拦截不了一些变化,比如给对象添加新属性,是不能拦截的,
vue2中给每一个数组元素绑定上监听,实际消耗很大,而受益并不大。只是Vue内部重写了函数
所以vue3利用proxy代理器来进行数据劫持
参考
通过 Proxy 创建对于原始对象的代理对象,从而在代理对象中使用 Reflect 达到对于 JavaScript 原始操作的拦截。
why?
const parent = {
name: '19Qingfeng',
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
return Reflect.get(target, key);
// 这里相当于return target[key]
},
};
const proxy = new Proxy(parent, handler);
const obj = {
name: 'wang.haoyu',
};
// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(obj, proxy);
console.log(obj.value); // 19Qingfeng
我们稍微分析下上边的代码:
当我们调用 obj.value 时,由于 obj 本身不存在 value 属性。
它继承的 proxy 对象中存在 value 的属性访问操作符,所以会发生屏蔽效果。此时会触发 proxy 上的 get value() 属性访问操作。
同时由于访问了 proxy 上的 value 属性访问器,所以此时会触发 get 陷阱。进入陷阱时,target 为源对象也就是 parent ,key 为 value 。
陷阱中返回 Reflect.get(target,key) 相当于 target[key]。此时,不知不觉中 this 指向在 get 陷阱中被偷偷修改掉了!!
原本调用方的 obj 在 get 陷阱中被修改成为了对应的 target 也就是 parent 。自然而然打印出了对应的 parent[value] 也就是 19Qingfeng 。
所以要用Reflect将this改正确
即,get里面returnReflect.get(target,key,receiver)
props是对外接口,在面向对象编程中,父类向子类传递的props类似公有属性,是允许外部访问的。父组件通过这个值来影响子组件的状态,在子组件中修改props是会影响到父组件的,那如果子组件可以来修改props,父组件是不知道他的数据被改了的,就会造成父子组件状态不同步,状态混乱引发问题。所以就要用emit来告知父组件,让父组件自己来对数据进行修改。
因此,最优的方案就是约定props不允许直接修改,是只读的(虽然js特性的引用类型可以绕过去,但不推荐),需通过emit事件通过父组件修改,让父组件感知变化。也就是单项数据流。
https://www.zhihu.com/question/468676903
,
重新渲染分为两种情况,
第一种是props直接修改了值,比如例子中的modyfyAge,那个直接触发了子组件props数据age的setter,所以子组件重新渲染;
第二种,props传递的数据是一个对象hobby,修改了对象的某个属性的值game,这个时候子组件也会重新渲染,但是它不是因为子组件props数据hobby的setter被触发,而是因为子组件的渲染watcher被依赖收集到了父组件的hobby的getter中了。hobby发生变化,触发setter,让子组件也重新渲染了。
MVC(单向数据改变)
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。Model是负责存储页面的数据,View是负责页面的显示功能,Controller是处理用户交互的部分(数据的改变引起视图的改变)可以使用onchange来改变数据
MVC的思想:一句话描述就是Controller负责将Model的数据用View显示出来,换句话说就是在Controller里面把Model的数据赋值给View。
MVVM(双向数据改变)
ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系,因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
优点:分离视图和模型,减少了代码的耦合度,自动更新DOM和数据
缺点:当一个视图的状态很多的时候,viewmodule的构建和维护成本比较高
在vue3以前,vue会有一些痛点:对状态的操作的逻辑是分散在不同的生命周期里的,不直观;API的设计风格是分散的option风格API,不利于逻辑的提取和复用;因为this指向的问题对于ts不能友好的兼容;这些问题,所以改进的目标就是:
vue2 vs vue 3
vue的话它组件的template,css,还有一些逻辑功能是分开的,写起来好写,可读性和维护性也高
他是双向数据绑定,就可以不用频繁的操作DOM,把更多的精力用在逻辑上
vue在网上的资料也多
Vue 从一开始的定位就是尽可能的降低前端开发的门槛,让更多的人能够更快地上手开发。很多时候我更希望自己做的东西能帮到那些中小型企业和个人开发者。所以从设计的角度上来说,Vue 首先考虑的是假设用户只掌握了 web 基础知识 (HTML, CSS, JS) 的情况下,如何能够最快理解和上手,实现一个看得见摸得着的应用。(尤雨溪)
数据流,vue是响应式的,支持双向数据绑定,而react是函数式的,提倡单向数据流
虚拟DOM
组件化,vue和react最大的不同就是模板的编写
监听数据变化的实现原理不同
高阶组件
可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的
不同点:
computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值;
method 调用总会执行该函数。
slot又分三类,默认插槽,具名插槽和作用域插槽
组件的插槽也是为了让我们封装的组件更加具有扩展性。
让使用者可以决定组件内部的一些内容到底展示什么。
比如:导航栏组件,可以在里面做插槽,然后使用的时候按照需要来替换插槽
匿名插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只显示一个匿名插槽。
也就是带有name属性的slot,一个组件可以出现多个具名插槽。
具名插槽:带有name的插槽,一个组件可以有多个具名插槽,父级组件通过对应的slot属性来对应子组件的插槽的name来替换
作用域插槽:父组件替换插槽的标签,但是内容由子组件来提供。该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
在父组件使用我们的子组件时,从子组件中拿到数据:我们通过获取到slotProps属性
在通过slotProps.data就可以获取到刚才我们传入的data了
filters不会修改数据,而是过滤数据,改变用户看到的输出
使用场景:
需要格式化数据的情况,比如需要处理时间、价格等数据格式的输出格式。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。
在Vue中,还可以是用keep-alive来缓存页面,当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行 被包裹在keep-alive中的组件的状态将会被保留:
v-html会先移除节点下的所有节点,将字符串解析成html标签,就是将innerHTML值设置为解析好的标签
v-once组件只会渲染一次,之后里面的数据变化他也不会变化
v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
v-cloak:解决初始化页面时页面闪动问题
可以用v-cloak来设置浏览器没有编译完成时组件的样式
v-module实际上是一个语法糖,它的原理就是使用v-bind动态绑定一个值(message),然后使用v-on来给当前的元素绑定input事件
<input type="text" v-bind:value v-on:input="message=$event.target.value">
js中的对象是引用类型,多个实例引用一个对象时,当一个实例的对象修改时,别的实例都会改变。在Vue中是想要更多的复用组件,每个组件中的数据都要是独立的,所以组件的data值不能是一个对象,使用函数的形式,在复用组件的时候每次都会返回一个新的data,这样每个组件都有自己的数据空间,各自维护,互不影响
keep-alive是vue内置的组件,能在组件切换时保存它的状态在内存中,防止重复渲染DOM。
vue为了高效率的更新DOM,Vue不可能对每一个数据变化都做一次渲染,它会把这些变化先放在一个异步的队列当中,同时它还会对这个队列里面的操作进行去重,然后在一次事件循环结束之后更新DOM,nextTick就是在一次DOM更新完毕之后调用的
nextTick实现原理:
nextTick会将回调函数放在异步任务重中,他是使用Promise.then、MutationObserver和setImmediate或者setTimout来让callback放入异步队列的,这样callbac就会在同步代码执行完了之后调用,此时就可以操作更新好的DOM了
源码是使用三个参数来做的
callback:我们要执行的操作,可以放在这个函数当中,我们没执行一次$nextTick就会把回调函数放到一个异步队列当中;
pending:标识,用以判断在某个事件循环中是否为第一次加入,第一次加入的时候才触发异步执行的队列挂载
timerFunc:用来触发执行回调函数,也就是Promise.then或MutationObserver或setImmediate 或setTimeout的过程
还有 n e x t T i c k 和 n e x t T i c k 区别就是 n e x t T i c k 多了一个 c o n t e x t 参数,用来指定上下文。但两个的本质是一样的, nextTick和nextTick区别就是nextTick多了一个context参数,用来指定上下文。但两个的本质是一样的, nextTick和nextTick区别就是nextTick多了一个context参数,用来指定上下文。但两个的本质是一样的,nextTick是实例方法,nextTick是类的静态方法而已;实例方法的一个好处就是,自动给你绑定为调用实例的this罢了。
使用场景:
不会立即渲染,Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。当一次事件循环结束之后才会重新渲染视图
Vue 中给 data 中的对象属性添加一个新的属性时,可以发现对象上确实添加了新属性,但是视图却没有发生变化,因为创建Vue时,并没有这个新属性,他不是响应式的,因为vue2使用Object.definePropertype来进行数据劫持的,他并不能劫持到添加新属性,所以不能引起更新视图(vue3使用的是proxy来劫持的)
解决办法:使用Vue全局API this.
s
e
t
(
)
来添加新属性
t
h
i
s
.
set()来添加新属性 this.
set()来添加新属性this.set(this.obj, ‘name’, ‘kang’)
因为vue使用Object.definePropertype来进行数据劫持的,他不能侦听到数组内部的变化,比如长度,所以vue就一些数组的函数进行了重写,会获取到数组的obeserver对象,如果有新的值,就侦听新值的变化
使其改变数组的同时可以引起视图的变化
vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所有需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素
Vue的模板编译在$mount之后,通过compile方法,经过parse、optimize、generate方法,最后生成render function来生成虚拟DOM,虚拟
DOM通过diff算法,来更新DOM。
子组件不可以直接改变父组件的数据。这样做主要是为了维护父子组件的单向数据流,即父级 props 的更新会流向子组件,但是反过来则不行,子组件只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由普通对象变成响应式对象,在这个过程中便会进行依赖收集的相关逻辑,具体是通过一个Dep类,每个响应式对象就有一个Dep,每当templete中有一个双向绑定的数据,就会通过watcher往dep中添加一个依赖,
编码阶段
在页面deactivated的时候调用this.$destroy(‘组件名’)将此组件进行销毁
另外会有在keep-alive中的路由组件会有的周期,actived和deactivated
当组件切换走的时候,会将组件的状态添加进缓存中,此时会调用deactived,当组件被切换回来的时候,会去缓存中找这个组件,触发active
渲染过程
父子间先创建,父子间beforeMount后子组件创建,子组件先挂载
更新过程
父子子父
销毁过程
父子子父
父组件向子组件:props
子组件向父组件:子组件使用
e
m
i
t
发送事件,父组件用
v
−
o
n
监听父组件访问子组件:使用
emit发送事件,父组件用v-on监听 父组件访问子组件:使用
emit发送事件,父组件用v−on监听父组件访问子组件:使用children或
r
e
f
s
子组件访问父组件:使用
refs 子组件访问父组件:使用
refs子组件访问父组件:使用parent
非父子组件使用eventBus
在前端技术早期,一个 url 对应一个页面,如果要从 A 页面切换到 B 页面,那么必然伴随着页面的刷新。这个体验并不好,后来,改变发生了——Ajax 出现了,它允许人们在不刷新页面的情况下发起请求;与之共生的,还有“不刷新页面即可更新页面内容”这种需求。在这样的背景下,出现了 SPA(单页面应用)。但是SPA 其实并不知道当前的页面“进展到了哪一步”。为了解决这个问题,前端路由出现了。前端路由可以帮助我们在仅有一个页面的情况下,“记住”用户当前走到了哪一步,
具体做法就是 :拦截用户的刷新操作,避免服务端盲目响应、返回不符合预期的资源内容。
把刷新这个动作完全放到前端逻辑里消化掉。前端路由会感知 URL 的变化,根据这些变化、用 JS 去给它生成不同的内容
使用箭头函数和require或者import 动态加载
Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。
这里的 hash 就是指 url 尾巴后的 # 号以及后面的字符。hash 也 称作 锚点,本身是用来做页面定位的。由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件,浏览器的进后退也能对其进行控制,所以人们在 html5 的 history 出现前,基本都是使用 hash 来实现前端路由的。
history模式的URL中没有#,用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
如果想要切换到history模式,就要进行以下配置(后端也要进行配置):
const router = new VueRouter({
mode: ‘history’,
routes: […]
})
因为在history模式下,只是动态的通过js操作window.history来改变浏览器地址栏里的路径,并没有发起http请求,但是当我直接在浏览器里输入这个地址的时候,就一定要对服务器发起http请求,但是这个目标在服务器上又不存在,所以会返回404
解决方法:
$route对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query对象等。
r
o
u
t
e
r
对象是全局路由的实例
b
a
c
k
(
)
,
g
o
(
)
,
r
e
p
l
a
c
e
t
h
i
s
.
router对象是全局路由的实例back(),go(),replace this.
router对象是全局路由的实例back(),go(),replacethis.router.go(-1)
url地址显示:query更加类似于ajax中get传参,params则类似于post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示
vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
导航钩子的三个参数解析:
to: 即将要进入的目标的路由对象.
from: 当前导航即将要离开的路由对象.
next: 调用该方法后, 才能进入下一个钩子
采用了全局单例模式,将组件的共享状态抽离出来管理,让每个组件都可以共享状态
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理 应用的所有组件的状态
简单来说就是把需要共享的变量全部存储在一个对象里面。然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。并且这个对象里的数据都是响应式的,当组件从store中获取了数据,如果store里的数据发生改变,组件里的数据也会发生改变
有五种,分别是 State、 Getter、Mutation 、Action、 Module
state => 基本数据(数据源存放地)
getters => 从基本数据派生出来的数据
mutations => 提交更改数据的方法,同步
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex
使用场景
比如用户的登录状态、用户名称、头像、地理位置信息等等。
比如商品的收藏、购物车中的物品等等。
这些状态信息,都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
核心流程
组件触发一些动作或事件,也就是actions,想要改变状态或获取数据,但是在vuex中状态是集中管理的,不能直接修改数据,所以就会把这个动作或事件(actions)提交给mutation,然后mutatiion去修改state中的数据,当state数据改变后,就会重新渲染到vue组件去,组件展示新数据。
mutation是用来改变状态的,有devtools可以捕捉状态变化的快照,mutation里面是异步操作的话,devtools就不知道什么时候回调函数被调用,实质上任何在回调函数中进行的状态的改变都是不可追踪的。所以可以将异步代码写在action中,就各司其职
在严格模式下,如果state状态不是因为mutation引起的,就会抛出错误,这样可以保证所有的状态变化都会被调试工具追踪到,在Vue.store中设置
const store = new Vuex.Store({
strict:true,
})
虚拟DOM本身是js对象。 在代码渲染到页面之前,vue会把代码转换成一个对象(虚拟 DOM)。以对象的形式来描述真实DOM结构,最终渲染到页面。在每次数据发生变化前,虚拟DOM都会缓存一份,变化之时,现在的虚拟DOM会与缓存的虚拟DOM进行比较。在vue内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。
虚拟 DOM 真正的价值从来都不是性能,而是不管数据怎么变化,都可以用最小的代价来更新 DOM,而且掩盖了底层的 DOM
操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护
虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的。使用原生API操作DOM的时候,比如当你在一次操作时,需要更新10个DOM节点,理想状态是一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。但是使用虚拟DOM的时候,就不会直接操作DOM,而是生成一个新的js对象后,使用diff算法跟之前的虚拟DOM作比较,最终将有差异的地方一次性运用到真正的DOM、中去渲染。
js对象模拟DOM节点的好处是,页面的更新可以先全部反映在js对象上,操作内存中的js对象的速度显然要快多了
diff算法只是比较同一层的节点,放弃跨级比较,降低了时间复杂度
具体比较是:
key是用来唯一标识一个元素的
因为使用index作为key的时候,比如删除中间的一个节点,后面的节点的key都会发生变化,都要重新渲染,影响性能
vue会给当前组件的每一个DOM添加一个不重复的data值 <div data-v-[随机数] class=‘moumou’>
给css选择器中添加上相应的data属性,这样样式只会生效在当前组件
好处:让组件间的样式独立起来,互不影响
坏处:这个样式只对当前组件生效,不会对组件里的子组件生效,因为只会子组件第一个DOM节点加上data属性,其余的不添加,
可以用/deep/来解决
实际上,使用了样式穿透违反了scoped属性的意义,可以在vue组件中添加不含scoped属性的style标签,给组件最外层DOM节点添加唯一的class类名,使用该类名来修改其他组件的样式,达到既不产生样式全局污染也能修改其他组件样式的效果。
获取和遍历操作
当涉及组件嵌套,在父组件中使用props.children把所有子组件显示出来
this.props.children的值有三种状态
如果当前组件没有子节点,它的值就是undefined
如果当前组件只有一个子节点,它的值就是我object
如果当前组件有多个子节点,它的值就是array
当this.props.children的值不是数组时,使用js的map会报错,React提供了API React.Children来处理this.props.children,它已经将this.props.children的所有情况考虑在内了。
JavaScript中的map不会对为null或者undefined的数据进行处理,而React.Children.map中的map可以处理React.Children为null或者undefined的情况。
React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。(例子,父组件中有两个input子组件,如果想在第一个输入框输入数据,来改变第二个输入框的值)
StrictMode 是一个用来突出显示应用程序中潜在问题的工具,只检测子组件,不检测自组建的子组件。
StrictMode 目前有助于:
react:包含react所必须的核心代码
react-dom:react渲染在不同平台所需要的核心代码
babel:将jsx转换成React代码的工具
不是,每个jsx元素只是react.createElement的语法糖,babel会将jsx写法转成react.creatrElemene,使用jsx完成的事情也可以完全用js来代替。
JSX 是一个 JavaScript 的语法扩展,结构类似 XML。JSX 主要用于声明 React 元素,但 React 中并不强制使用 JSX。即使使用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。
装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案
类组件和函数组件之间,是面向对象和函数式编程这两套不同的设计思想之间的差异。而函数组件更加契合 React 框架的设计理念,React 组件本身的定位就是函数,一个输入数据、输出 UI 的函数。
如果 useState 返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净
如果 useState 返回的是对象,在解构对象的时候必须要和 useState 内部实现返回的对象同名,想要使用多次的话,必须得设置别名才能使用返回值
1.如果项目体量较小,只是需要一个公共的store存储state,而不讲究使用action来管理state,那context完全可以胜任。反之,则是redux的优点。
2.context的缺点:因为没有了action,state的值都是被直接修改,state的数据安全性不及redux。同时也不能使用redux的中间件,比如thunk/saga,在一些异步的情况需要自己来处理。
3.Redux有devtools可监测数据的变化
共同作用:
1.仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。
两者区别:
1.useMemo 计算结果是 return 回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态
2.useCallback 计算结果是 函数, 主要用于 缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费
参考
只要你进入了 react 的调度流程,那就是异步的。只要你没有进入 react 的调度流程,那就是同步的。什么东西不会进入 react 的调度流程? setTimeout setInterval ,直接在 DOM 上绑定原生事件等。这些都不会走 React 的调度流程,你在这种情况下调用 setState ,那这次 setState 就是同步的。 否则就是异步的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。