17-实现组件emit

116 阅读3分钟

思考

setup可选的第二个参数是context,里面有emit

  • setup接收一个对象,里面有emit
  • 子组件触发emit(xx),父组件可以通过onXx接收回调
  • emit还可以传数据,emit(xx,...arg)

写个例子

/*
 * @Author: Lin zefan
 * @Date: 2022-03-21 21:46:14
 * @LastEditTime: 2022-03-26 12:29:28
 * @LastEditors: Lin zefan
 * @Description:
 * @FilePath: \mini-vue3\example\helloWorld\App.js
 *
 */

import { h } from "../../lib/mini-vue.esm.js";

const Foo = {
  setup(props, { emit }) {
    const emitAdd = () => {
      console.log("子组件调用emit/add");
      emit(
        "add",
        {
          a: 1,
        },
        2
      );
    };

    const addCount = () => {
      console.log("子组件调用emit/add-count");
      emit("add-count", 2);
    };

    return {
      emitAdd,
      addCount,
    };
  },
  render() {
    return h("div", {}, [
      h(
        "button",
        {
          onClick: this.emitAdd,
        },
        "emit为add"
      ),
      h(
        "button",
        {
          onClick: this.addCount,
        },
        "emit为add-count"
      ),
    ]);
  },
};
export default {
  name: "App",
  setup() {
    const onAdd = (...arg) => {
      console.log("父组件接收到emit/add", arg);
    };
    const onAddCount = (...arg) => {
      console.log("父组件接收到emit/add-count", arg);
    };
    return {
      onAdd,
      onAddCount,
    };
  },

  render() {
    return h("div", {}, [
      h(Foo, {
        onAdd: this.onAdd,
        onAddCount: this.onAddCount,
      }),
    ]);
  },
};

实现setup注册emit

component.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-21 22:08:11
 * @LastEditTime: 2022-03-26 12:57:43
 * @LastEditors: Lin zefan
 * @Description: 处理组件类型
 * @FilePath: \mini-vue3\src\runtime-core\component.ts
 *
 */
 
import { emit } from "./componentEmit";

// 初始化Component结构
function createComponentInstance(initVNode) {
  const component = {
    vnode: initVNode,
    type: initVNode.type,
    proxy: null,
    setupState: {},
    props: {},
    emit: () => {},
  };

  /** 注册emit
   * 1. 通过bind把当前实例给到emit函数
   */
  component.emit = emit.bind(null, component) as any;

  return component;
}

// 初始化setup返回值
function setupStatefulComponent(instance, container) {
  /** 获取用户声明的setup函数过程
   * 1. 前面通过createApp将根组件转换为vnode
   * 2. 之后通过createComponentInstance将vnode进行二次包装
   * 3. 最后可以通过instance.type 获取根组件(rootComponent)
   */
  const component = instance.type;

  const { setup } = component;
  if (setup) {
    /**
     * 1. setup接收props、context
     * 2. 执行setup
     */
    const setupResult = setup(shallowReadonly(instance.props), {
      emit: instance.emit,
    });
    handleSetupResult(instance, setupResult);
  }
}

componentEmit.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-26 11:27:22
 * @LastEditTime: 2022-03-26 12:34:05
 * @LastEditors: Lin zefan
 * @Description: emit
 * @FilePath: \mini-vue3\src\runtime-core\componentEmit.ts
 *
 */

export function emit({ props }, event) {
  /**
   * 1. event 为当前emit触发的事件名
   * 2. 根据事件名去找到props注册的对应事件,进行调用
   */
 const capitalize = (event: string) => {
    // 取出首字母,转换为大写 + 切割掉首字母
    return event ? event.charAt(0).toLocaleUpperCase() + event.slice(1) : "";
  };

  const handlerEventName = (event) => {
    return "on" + capitalize(event);
  };
  // 调用props的对应事件
  const handler = props[handlerEventName(event)];
  handler && handler();
}

实现emit接收参数

componentEmit.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-26 11:27:22
 * @LastEditTime: 2022-03-26 12:34:05
 * @LastEditors: Lin zefan
 * @Description: emit
 * @FilePath: \mini-vue3\src\runtime-core\componentEmit.ts
 *
 */

export function emit({ props }, event, ...arg) {
  /**
   * 1. event 为当前emit触发的事件名
   * 2. 根据事件名去找到props注册的对应事件,进行调用
   * 3. arg是emit接收的数据
   */
 const capitalize = (event: string) => {
    // 取出首字母,转换为大写 + 切割掉首字母
    return event ? event.charAt(0).toLocaleUpperCase() + event.slice(1) : "";
  };

  const handlerEventName = (event) => {
    return "on" + capitalize(event);
  };
  // 调用props的对应事件
  const handler = props[handlerEventName(event)];
  handler && handler(...arg);
}

兼容短横线格式

/*
 * @Author: Lin zefan
 * @Date: 2022-03-26 11:27:22
 * @LastEditTime: 2022-03-26 13:04:08
 * @LastEditors: Lin zefan
 * @Description: emit
 * @FilePath: \mini-vue3\src\runtime-core\componentEmit.ts
 *
 */

import { handlerEventName } from "../shared/index";

export function emit({ props }, event, ...arg) {
  /**
   * 1. event 为当前emit触发的事件名
   * 2. 根据事件名去找到props注册的对应事件,进行调用
   * 3. arg是emit接收的数据
   */
  //   const handler = props[handlerEventName(event)];
  //   handler && handler(...arg);

  // 将 -字母 转换为 大驼
  const camelize = (event: string) => {
    /** replace的第二个参数为函数
     * 参数一:正则匹配的结,即-x
     * 参数二:为\w
     */
    // replace的函数回调有个特点,正则里面只要有()包裹的,判断为分组($1),都会单独返会一个结果,所以这里参数2是-后的字母,如果(\w)换成\w,那参数2会是匹配结果的下标
    return event.replace(/-(\w)/g, (_, str: string) => {
      return str.toUpperCase();
    });
  };

  const capitalize = (event: string) => {
    // 取出首字母,转换为大写 + 切割掉首字母
    return event ? event.charAt(0).toLocaleUpperCase() + event.slice(1) : "";
  };

  const handlerEventName = (event) => {
    return "on" + capitalize(camelize(event));
  };

  // other code
}