手写 mini-vue3 - 实现 props & emit(五)

31 阅读2分钟

实现事件注册 & props 添加到标签

// runtime-core/renderer.ts
function mountElement() {
    // 1. 创建标签
    const el = (vnode.el = hostCreateElement(vnode.type));
  
    // 2. 处理标签内容
    const { children, shapeFlag } = vnode;
    // children 是字符串
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      el.textContent = children;
      // children 是数组
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      mountChildren(vnode.children, el, parentComponent, anchor);
    }
  
    const { props } = vnode;
  
+    // 3. 处理标签的 props 属性
+    // 循环 props
+    for (const key in props) {
+      const val = props[key];
+      // 处理注册事件和 props
+      hostPatchProp(el, key, null, val);
+    }
  
    // 4. 把 el 添加到 container
    container.append(el);
}
function hostPatchProp(el, key, prevVal, nextVal) {
  const isOn = (key: string) => /^on[A-Z]/.test(key);
  if (isOn(key)) {
    const event = key.slice(2).toLowerCase();
    el.addEventListener(event, nextVal);
  } else {
    if(nextVal === undefined || nextVal === null) {
      el.removeAttribute(key);
    } else {
      el.setAttribute(key, nextVal);
    }
  }
}

props 挂载到 instance

// runtime-core/component.ts
export function createComponentInstance(vnode, parent) {
  const component = {
    props: {},
    ...
  };
  return component;
}

export function setupComponent(instance, container) {
  // props 挂载到 instance
  initProps(instance, instance.vnode.props);
  // 处理 setup 函数参数
  setupStatefulComponent(instance);
}

props 作为参数传给 setup

// runtime-core/componentProps.ts
export function initProps(instance, rawProps) {
  instance.props = rawProps || {};
}
// runtime-core/component.ts
function setupStatefulComponent(instance) {
  const Component = instance.type;
  const { setup } = Component;
  
  // 实现组件对象的代理
  ...

  if(setup) {
    // props 作为只读参数传给 setup 函数
    const setupResult = setup(shallowReadonly(instance.props));
    
    // 处理 setup函数的返回结果
    ...
  }
}

$porps 挂载到组件代理对象

// runtime-core/componentPublicInstance.ts
const PublicPropertiesMaps = {
  $props: (i) => i.props,
}

export const PublicInstanceProxyHandlers = {
  get({ _: instance }, key) {
    const { setupState, props } = instance;
    
    const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);

    if(hasOwn(setupState, key)) {
      return setupState[key];
    } else if(hasOwn(props, key)) {
      return props[key];
    }

    const publicGetter = PublicPropertiesMaps[key];
    if(publicGetter) {
      return publicGetter(instance);
    }
  }
}

实现 emit

emit 挂载到 instance

import { emit } from './componentEmit';
export function createComponentInstance(vnode, parent) {
  const component = {
    emit: () => {},
  };
  component.emit = emit.bind(null, component) as any;
  return component;
}

props 中找到事件名称并执行事件

// componentEmit.ts
import { camelize, toHandlerKey } from '../shared/index';

// 接收 emit 的 event 事件名参数
export function emit(instance, event, ...args) {
  const { props } = instance;
  // props onAdd event -> add

  // 转化 event `aa-bb` 以匹配 props 中对应的 `onAaBb`
  const handlerName = toHandlerKey(camelize(event));
  const hanlder = props[handlerName];

  // 执行事件
  hanlder && hanlder(...args);
}
// shared/index.ts
// 改为驼峰
export const camelize = (str: string) => {
  return str.replace(/-(\w)/g, (_, c: string) => {
    return c ? c.toUpperCase() : '';
  })
}
// shared/index.ts
// 添加 on 前缀,并事件首字母大写
export const toHandlerKey = (str) => {
  return str ? 'on' + capitalize(str) : '';
}

const capitalize = (str: string) => {
  return str ? str.charAt(0).toUpperCase() + str.slice(1) : '';
}

emit 作为参数传给 setup

// runtime-core/component.ts
function setupStatefulComponent(instance) {
  const Component = instance.type;
  const { setup } = Component;

  // 实现组件对象的代理
  ...

  if(setup) {
    const setupResult = setup(shallowReadonly(instance.props), {
      emit: instance.emit,
    });
    
    // 处理 setup函数的返回结果
    ...
  }
}