reactivity
// 导出方法,不实现功能
export {
reactive,
shallowReactive,
shallowReadonly,
readonly
} from './reactive'
export {
effect
} from './effect'
export {
ref,
shallowRef,
toRef,
toRefs
} from './ref'
export {
computed
} from './computed'
实现上面的方法
import { isObject } from "@vue/shared"
import {
mutableHandlers,
shallowReactiveHandlers,
readonlyHandlers,
shallowReadonlyHandlers
} from './baseHandlers'
export function reactive(target){
return createReactiveObject(target,false,mutableHandlers)
}
export function shallowReactive(target){
return createReactiveObject(target,false,shallowReactiveHandlers)
}
export function readonly(target){
return createReactiveObject(target,true,readonlyHandlers)
}
export function shallowReadonly(target){
return createReactiveObject(target,true,shallowReadonlyHandlers)
}
// 是不是仅读 是不是深度, 柯里化 new Proxy() 最核心的需要拦截 数据的读取和数据的修改 get set
const reactiveMap = new WeakMap(); // 会自动垃圾回收,不会造成内存泄漏, 存储的key只能是对象
const readonlyMap = new WeakMap();
export function createReactiveObject(target,isReadonly,baseHandlers){
// 如果目标不是对象 没法拦截了,reactive这个api只能拦截对象类型
if( !isObject(target)){
return target;
}
// 如果某个对象已经被代理过了 就不要再次代理了 可能一个对象 被代理是深度 又被仅读代理了
const proxyMap = isReadonly? readonlyMap:reactiveMap
const existProxy = proxyMap.get(target);
if(existProxy){
return existProxy; // 如果已经被代理了 直接返回即可
}
const proxy = new Proxy(target,baseHandlers);
proxyMap.set(target,proxy); // 将要代理的对象 和对应代理结果缓存起来
return proxy;
}
对应的proxy代理的方法
// 实现 new Proxy(target, handler)
import { extend, hasChanged, hasOwn, isArray, isIntegerKey, isObject } from "@vue/shared/src";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOrTypes } from "./operators";
import { reactive, readonly } from "./reactive";
const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const showllowReadonlyGet = createGetter(true, true);
const set = createSetter();
const shallowSet = createSetter(true);
export const mutableHandlers = {
get,
set
}
export const shallowReactiveHandlers = {
get: shallowGet,
set: shallowSet
}
let readonlyObj = {
set: (target, key) => {
console.warn(`set on key ${key} falied`)
}
}
export const readonlyHandlers = extend({
get: readonlyGet,
}, readonlyObj)
export const shallowReadonlyHandlers = extend({
get: showllowReadonlyGet,
}, readonlyObj)
// 是不是仅读的,仅读的属性set时会报异常
// 是不是深度的
function createGetter(isReadonly = false, shallow = false) { // 拦截获取功能
return function get(target, key, receiver) { // let proxy = reactive({obj:{}})
// proxy + reflect
// 后续Object上的方法 会被迁移到Reflect Reflect.getProptypeof()
// 以前target[key] = value 方式设置值可能会失败 , 并不会报异常 ,也没有返回值标识
// Reflect 方法具备返回值
// reflect 使用可以不使用 proxy es6语法
const res = Reflect.get(target, key, receiver); // target[key];
if(!isReadonly){
// 收集依赖,等会数据变化后更新对应的视图
console.log('执行effect时会取值','收集effect')
track(target,TrackOpTypes.GET,key)
}
if(shallow){
return res;
}
if(isObject(res)){ // vue2 是一上来就递归,vue3 是当取值时会进行代理 。 vue3的代理模式是懒代理
return isReadonly ? readonly(res) : reactive(res)
}
return res;
}
}
function createSetter(shallow = false) { // 蓝爵设置功能
return function set(target, key, value, receiver) {
const oldValue = target[key]; // 获取老的值
let hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target,key);
const result = Reflect.set(target, key, value, receiver); // target[key] = value
if(!hadKey){
// 新增
trigger(target,TriggerOrTypes.ADD,key,value);
}else if(hasChanged(oldValue,value)){
// 修改
trigger(target,TriggerOrTypes.SET,key,value,oldValue)
}
// 我们要区分是新增的 还是修改的 vue2 里无法监控更改索引,无法监控数组的长度变化 -》 hack的方法 需要特殊处理
// 当数据更新时 通知对应属性的effect重新执行
return result;
}
}
import { isArray, isIntegerKey } from "@vue/shared/src";
import { TriggerOrTypes } from "./operators";
export function effect(fn, options: any = {}) {
// 我需要让这个effect变成响应的effect,可以做到数据变化重新执行
const effect = createReactiveEffect(fn, options);
if (!options.lazy) { // 默认的effect会先执行
effect(); // 响应式的effect默认会先执行一次
}
return effect;
}
let uid = 0;
let activeEffect; // 存储当前的effect
const effectStack = []
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
if (!effectStack.includes(effect)) { // 保证effect没有加入到effectStack中
try {
effectStack.push(effect);
activeEffect = effect;
return fn(); // 函数执行时会取值 会执行get方法
} finally {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
}
effect.id = uid++; // 制作一个effect标识 用于区分effect
effect._isEffect = true; // 用于标识这个是响应式effect
effect.raw = fn; // 保留effect对应的原函数
effect.options = options; // 在effect上保存用户的属性
return effect;
}
// 让,某个对象中的属性 收集当前他对应的effect函数
const targetMap = new WeakMap();
export function track(target, type, key) { // 可以拿到当前的effect
// activeEffect; // 当前正在运行的effect
if (activeEffect === undefined) { // 此属性不用收集依赖,因为没在effect中使用
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
}
}
// 找属性对应的effect 让其执行 (数组、对象)
export function trigger(target, type, key?, newValue?, oldValue?) {
// 如果这个属性没有 收集过effect,那不需要做任何操作
const depsMap = targetMap.get(target);
if (!depsMap) return;
const effects = new Set(); // 这里对effect去重了
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => effects.add(effect));
}
}
// 我要将所有的 要执行的effect 全部存到一个新的集合中,最终一起执行
// 1. 看修改的是不是数组的长度 因为改长度影响比较大
if (key === 'length' && isArray(target)) {
// 如果对应的长度 有依赖收集需要更新
depsMap.forEach((dep, key) => {
if (key === 'length' || key > newValue) { // 如果更改的长度 小于收集的索引,那么这个索引也需要触发effect重新执行
add(dep)
}
})
} else {
// 可能是对象
if (key !== undefined) { // 这里肯定是修改, 不能是新增
add(depsMap.get(key)); // 如果是新增
}
// 如果修改数组中的 某一个索引 怎么办?
switch (type) { // 如果添加了一个索引就触发长度的更新
case TriggerOrTypes.ADD:
if (isArray(target) && isIntegerKey(key)) {
add(depsMap.get('length'));
}
}
}
effects.forEach((effect: any) => {
if(effect.options.scheduler){
effect.options.scheduler(effect);
}else{
effect();
}
})
}
// weakMap {name:'zf',age:12} (map) =>{name => set(effect),age => set(effect)}
// {name:'zf',age:12} => name => [effect effect]
// 函数调用是一个栈型结构
// effect(()=>{ // effect1 [effect1]
// state.name -> effect1
// effect(()=>{ // effect2
// state.age -> effect2
// })
// state.address -> effect1
// })
ref:
import { hasChanged, isArray, isObject } from "@vue/shared/src";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOrTypes } from "./operators";
import { reactive } from "./reactive";
export function ref(value) {
// 将普通类型 变成一个对象 , 可以是对象 但是一般情况下是对象直接用reactive更合理
return createRef(value)
}
// ref 和 reactive的区别 reactive内部采用proxy ref中内部使用的是defineProperty
export function shallowRef(value) {
return createRef(value, true)
}
// 后续 看vue的源码 基本上都是高阶函数 做了类似柯里化的功能
const convert = (val) => isObject(val) ? reactive(val) : val
// beta 版本 之前的版本ref 就是个对象 ,由于对象不方便扩展 改成了类
class RefImpl {
public _value; //表示 声明了一个_value属性 但是没有赋值
public __v_isRef = true; // 产生的实例会被添加 __v_isRef 表示是一个ref属性
constructor(public rawValue, public shallow) { // 参数中前面增加修饰符 标识此属性放到了实例上
this._value = shallow ? rawValue : convert(rawValue)// 如果是深度 需要把里面的都变成响应式的
}
// 类的属性访问器
get value() { // 代理 取值取value 会帮我们代理到 _value上
track(this, TrackOpTypes.GET, 'value');
return this._value
}
set value(newValue) {
if (hasChanged(newValue, this.rawValue)) { // 判断老值和新值是否有变化
this.rawValue = newValue; // 新值会作为老值
this._value = this.shallow ? newValue : convert(newValue);
trigger(this, TriggerOrTypes.SET, 'value', newValue);
}
}
}
function createRef(rawValue, shallow = false) {
return new RefImpl(rawValue, shallow)
}
class ObjectRefImpl {
public __v_isRef = true;
constructor(public target, public key) {}
get value(){ // 代理
return this.target[this.key] // 如果原对象是响应式的就会依赖收集
}
set value(newValue){
this.target[this.key] = newValue; // 如果原来对象是响应式的 那么就会触发更新
}
}
// promisify
// promisifyAll
// 将某一个key对应的值 转化成ref
export function toRef(target, key) { // 可以把一个对象的值转化成 ref类型
return new ObjectRefImpl(target, key)
}
export function toRefs(object){ // object 可能传递的是一个数组 或者对象
const ret = isArray(object) ? new Array(object.length) :{}
for(let key in object){
ret[key] = toRef(object,key);
}
return ret;
}
runtime-core
// 组件中所有的方法
import { isFunction, isObject, ShapeFlags } from "@vue/shared/src";
import { PublicInstanceProxyHandlers } from "./componentPublicInstance";
export function createComponentInstance(vnode) {
// webcomponent 组件需要有“属性” “插槽”
const instance = { // 组件的实例
vnode,
type: vnode.type, // 用户写的对象
props: {}, // props attrs 有什么区别 vnode.props
attrs: {},
slots: {},
ctx: {},
data:{},
setupState: {}, // 如果setup返回一个对象,这个对象会作为setUpstate
render: null,
subTree:null, // render函数的返回结果就是subTree
isMounted: false // 表示这个组件是否挂载过
}
instance.ctx = { _: instance } // instance.ctx._
return instance;
}
export function setupComponent(instance) {
const { props, children } = instance.vnode; // {type,props,children}
// 根据props 解析出 props 和 attrs,将其放到instance上
instance.props = props; // initProps()
instance.children = children; // 插槽的解析 initSlot()
// 需要先看下 当前组件是不是有状态的组件, 函数组件
let isStateful = instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
if (isStateful) { // 表示现在是一个带状态的组件
// 调用 当前实例的setup方法,用setup的返回值 填充 setupState和对应的render方法
setupStatefulComponent(instance)
}
}
function setupStatefulComponent(instance) {
// 1.代理 传递给render函数的参数
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers as any)
// 2.获取组件的类型 拿到组件的setup方法
let Component = instance.type
let { setup } = Component;
// ------ 没有setup------
if(setup){
let setupContext = createSetupContext(instance);
const setupResult = setup(instance.props, setupContext); // instance 中props attrs slots emit expose 会被提取出来,因为在开发过程中会使用这些属性
handleSetupResult(instance,setupResult)
}else{
finishComponentSetup(instance);// 完成组件的启动
}
}
function handleSetupResult(instance,setupResult){
if(isFunction(setupResult)){
instance.render = setupResult
}else if(isObject(setupResult)){
instance.setupState = setupResult
}
finishComponentSetup(instance);
}
function finishComponentSetup(instance){
let Component = instance.type
if(!instance.render){
// 对template模板进行编译 产生render函数
// instance.render = render;// 需要将生成render函数放在实例上
if(!Component.render && Component.template){
// 编译 将结果 赋予给Component.render
}
instance.render = Component.render;
}
// 对vue2.0API做了兼容处理
// applyOptions
}
function createSetupContext(instance) {
return {
attrs: instance.attrs,
slots: instance.slots,
emit: () => { },
expose: () => { }
}
}
// 他们的关系涉及到后面的使用
// instance 表示的组件的状态 各种各样的状态,组件的相关信息
// context 就4个参数 是为了开发时使用的
// proxy 主要为了取值方便 =》 proxy.xxxx
import { effect } from "@vue/reactivity/src";
import { ShapeFlags } from "@vue/shared/src";
import { createAppAPI } from "./apiCreateApp"
import { createComponentInstance, setupComponent } from "./component";
import { queueJob } from "./scheduler";
import { normalizeVNode ,Text} from "./vnode";
export function createRenderer(rendererOptions) { // 告诉core 怎么渲染
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
} = rendererOptions
// -------------------组件----------------------
const setupRenderEfect = (instance, container) => {
// 需要创建一个effect 在effect中调用 render方法,这样render方法中拿到的数据会收集这个effect,属性更新时effect会重新执行
instance.update = effect(function componentEffect() { // 每个组件都有一个effect, vue3 是组件级更新,数据变化会重新执行对应组件的effect
if (!instance.isMounted) {
// 初次渲染
let proxyToUse = instance.proxy;
// $vnode _vnode
// vnode subTree
let subTree = instance.subTree = instance.render.call(proxyToUse, proxyToUse);
// 用render函数的返回值 继续渲染
patch(null, subTree, container);
instance.isMounted = true;
} else {
// diff算法 (核心 diff + 序列优化 watchApi 生命周期)
// ts 一周
// 组件库
// 更新逻辑
}
},{
scheduler:queueJob
});
}
const mountComponent = (initialVNode, container) => {
// 组件的渲染流程 最核心的就是调用 setup拿到返回值,获取render函数返回的结果来进行渲染
// 1.先有实例
const instance = (initialVNode.component = createComponentInstance(initialVNode))
// 2.需要的数据解析到实例上
setupComponent(instance); // state props attrs render ....
// 3.创建一个effect 让render函数执行
setupRenderEfect(instance, container);
}
const processComponent = (n1, n2, container) => {
if (n1 == null) { // 组件没有上一次的虚拟节点
mountComponent(n2, container);
} else {
// 组件更新流程
}
}
// ------------------组件 ------------------
//----------------- 处理元素-----------------
const mountChildren = (children, container) => {
for (let i = 0; i < children.length; i++) {
let child = normalizeVNode(children[i]);
patch(null, child, container);
}
}
const mountElement = (vnode, container) => {
// 递归渲染
const { props, shapeFlag, type, children } = vnode;
let el = (vnode.el = hostCreateElement(type));
if (props) {
for (const key in props) {
hostPatchProp(el, key, null, props[key]);
}
}
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, children);// 文本比较简单 直接扔进去即可
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el);
}
hostInsert(el, container);
}
const processElement = (n1, n2, container) => {
if (n1 == null) {
mountElement(n2, container);
} else {
// 元素更新
}
}
//----------------- 处理元素-----------------
// -----------------文本处理-----------------
const processText = (n1,n2,container) =>{
if(n1 == null){
hostInsert((n2.el = hostCreateText(n2.children)),container)
}
}
// -----------------文本处理-----------------
const patch = (n1, n2, container) => {
// 针对不同类型 做初始化操作
const { shapeFlag,type } = n2;
switch (type) {
case Text:
processText(n1, n2, container);
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container);
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
processComponent(n1, n2, container);
}
}
}
const render = (vnode, container) => {
// core的核心, 根据不同的虚拟节点 创建对应的真实元素
// 默认调用render 可能是初始化流程
patch(null, vnode, container)
}
return {
createApp: createAppAPI(render)
}
}
// createRenderer 目的是创建一个渲染器
// 框架 都是将组件 转化成虚拟DOM -》 虚拟DOM生成真实DOM挂载到真实页面上
import { createVNode } from "./vnode"
export function createAppAPI(render){
return function createApp(rootComponent,rootProps){ // 告诉他那个组件那个属性来创建的应用
const app = {
_props:rootProps,
_component:rootComponent,
_container:null,
mount(container){ // 挂载的目的地
// let vnode = {}
// render(vnode,container);
// 1.根据组件创建虚拟节点
// 2.将虚拟节点和容器获取到后调用render方法进行渲染
// 创造虚拟节点
const vnode = createVNode(rootComponent,rootProps)
// 调用render
render(vnode,container)
app._container = container
}
}
return app
}
}
// createVNode 创建虚拟节点
import { isArray, isObject, isString, ShapeFlags } from "@vue/shared/src";
export function isVnode(vnode){
return vnode.__v_isVnode
}
// h(‘div',{style:{color:red}},'children'); // h方法和createApp类似
export const createVNode = (type,props,children = null) =>{
// 可以根据type 来区分是组件 还是普通的元素
// 根据type来区分 是元素还是组件
// 给虚拟节点加一个类型
const shapeFlag = isString(type) ?
ShapeFlags.ELEMENT : isObject(type)?
ShapeFlags.STATEFUL_COMPONENT : 0
const vnode = { // 一个对象来描述对应的内容 , 虚拟节点有跨平台的能力
__v_isVnode:true,// 他是一个vnode节点
type,
props,
children,
component:null, // 存放组件对应的实例
el:null, // 稍后会将虚拟节点和真实节点对应起来
key: props && props.key,// diff算法会用到key
shapeFlag // 判断出当前自己的类型 和 儿子的类型
}
normalizeChildren(vnode,children);
return vnode;
}
function normalizeChildren(vnode,children){
let type = 0;
if(children == null){ // 不对儿子进行处理
} else if(isArray(children)){
type = ShapeFlags.ARRAY_CHILDREN;
} else{
type = ShapeFlags.TEXT_CHILDREN;
}
vnode.shapeFlag |= type
}
export const Text = Symbol('Text')
export function normalizeVNode(child){
if(isObject(child)) return child;
return createVNode(Text,null,String(child));
}
runtime-dom
// runtime-dom 核心就是 提供domAPI方法了
//操作节点、操作属性的更新
import { extend } from "@vue/shared/src";
import { nodeOps } from "./nodeOps"; // 对象
import { patchProp } from "./patchProp"; // 方法
import {createRenderer} from '@vue/runtime-core'
// 节点操作就是增删改查
// 属性操作 添加 删除 更新 (样式、类、事件、其他属性)
// 渲染时用到的所有方法
const rendererOptions = extend({ patchProp }, nodeOps)
// vue中runtime-core 提供了核心的方法 用来处理渲染的,他会使用runtime-dom中的api进行渲染
export function createApp(rootComponent, rootProps = null) {
const app = createRenderer(rendererOptions).createApp(rootComponent,rootProps)
let {mount} = app
app.mount = function (container) {
// 清空容器的操作
container = nodeOps.querySelector(container);
container.innerHTML = '';
mount(container); // 函数劫持
// 将组件 渲染成dom元素 进行挂载
}
return app;
}
export * from '@vue/runtime-core';
// 用户调用的是runtime-dom -> runtime-core
// runtime-dom 是为了解决平台差异 (浏览器的)