27-mini-vue provide-inject

实现 provide-inject 功能

作用

  • provideinject 用于在组件树中传递数据,避免通过 props 层层传递
  • provide 在祖先组件中提供数据,inject 在后代组件中注入数据
  • 常用于插件开发或跨层级组件通信

实现步骤

  1. 初始化 App.js
js 复制代码
// App.js
import { h, provide, inject } from "../lib/guide-mini-vue.esm.js";

const Provider = {
  name: "Provider",
  render() {
    return h("div", {}, [h("p", {}, "Provider"), h(Consumer)]);
  },
  setup() {
    provide("foo", "fooVal");
    provide("bar", "barVal");
  },
};

const Consumer = {
  name: "Consumer",
  render() {
    return h("div", {}, `Consumer: - ${this.foo} - ${this.bar}`);
  },
  setup() {
    const foo = inject("foo");
    const bar = inject("bar");
    return {
      foo,
      bar,
    };
  },
};

export default {
  name: "App",
  setup() {},
  render() {
    return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]);
  },
};
  1. 先实现一个简单的 provide-inject 功能, 存和取,数据放在组件实例上
js 复制代码
// /runtime-core/apiInject.js ✅ 
import { getCurrentInstance } from "./component";

export function provide(key, value) {
  // 存
  const currentInstance:any = getCurrentInstance() // 这个方法仅在 setup 函数中可以使用,所以 provide、inject 也只能在 setup 函数中使用
  if(currentInstance) {
    const { provides } = currentInstance
    provides[key] = value
  }
}

export function inject(key) {
  // 取
  const currentInstance:any = getCurrentInstance()
  if(currentInstance) {
    const parentProvides = currentInstance.parent.provides
    return parentProvides[key]
  }
}

// runtime-core/component.ts 
export function createComponentInstance(vnode, parent) { 
  // ✅ parent 用来传递父组件实例 往上回溯,renderer.ts 中好多十几个位置需要加上 parent 参数,形参名为 parentComponent, 到源头 patch(subTree, container, instance)
  console.log("createComponentInstance", parent);
  
  const component = {
    vnode,
    type: vnode.type,
    setupState: {},
    props: {},
    emit:()=>{},
    slots:{},
    provides: {}, // ✅ 用来存储 provide 的数据
    parent // ✅ 记录父组件实例
  }
  component.emit = emit.bind(null, component) as any // 这里 null 是this指向, component 作为第一个参数传入
  return component
}

// renderer.ts
`因为上面的函数 createComponentInstance 需要 parent 参数,所以这里也要传入 parentComponent,而且这个页面涉及的地方比较多,全部改成传入 parentComponent 参数`

// runtime-core/index.ts
export { provide, inject } from './apiInject'
  • 结果:我们实现了最简单的 provide-inject 功能,父组件通过 provide 存储数据,子组件通过 inject 获取数据
  • 思想:我们开发项目可以按照先实现一个简单的需求的版本,然后再慢慢完善它,属于小步走开发思想,防止我们过度设计,而且实现起来也没有压力。
  1. 我们在上面已经实现了一个父子组件之间的传递通过 provide-inject 功能,接下来我们实现祖孙组件之间的传递
  • 给 App.js 的 Provider 组件和 Consumer 组件中间再加一层组件 Middle---> ProviderTwo 这个组件
js 复制代码
// App.js
const ProviderTwo = {
  name: 'ProviderTwo',
  render() {
    return h('div', {}, [h('p', {}, "ProviderTwo"), h(Consumer)])
  },
  setup() {
  }
}
  • 对相关的祖孙传递逻辑进行实现
    • 问题?我们加上一层以后就无法在 App.js 的 Consumer 组件中获取到 Provider 组件中 provide 的数据了

      • 分析?因为我们现在的数据是存储在组件实例的 provides 对象上,而 Middle 组件实例的 provides 对象是空的,所以无法获取到 Provider 组件中 provide 的数据
      • 解决方案?
        • 让实例上的 provides 属性指向父组件实例的 provides 属性,这样就可以实现祖孙组件之间的传递了,代码如下:
      js 复制代码
      // runtime-core/component.ts
      export function createComponentInstance(vnode, parent) {
      console.log("createComponentInstance", parent);
      
        const component = {
          vnode,
          type: vnode.type,
          setupState: {},
          props: {},
          emit:()=>{},
          slots:{},
          provides: parent ? parent.provides : {}, // ✅ 让实例上的 provides 属性指向父组件实例的 provides 属性
          parent
        }
        component.emit = emit.bind(null, component) as any // 这里 null 是this指向, component 作为第一个参数传入
        return component
      }
      • 结果:我们实现了祖孙组件之间的传递
    • 我们继续实现更复杂一点的场景,我们在 Middle 组件中也使用 provide 提供一个 provide("foo", "fooTwo"),然后在 Consumer 组件中通过 inject 获取 foo 的值,看看会获取到哪个值

      js 复制代码
      // App.js
      const ProviderTwo = {
        name: 'ProviderTwo',
        render() {
          return h('div', {}, [h('p', {}, "ProviderTwo " + this.foo), h(Consumer)])
        },
        setup() {
          provide("foo", "fooTwo")
          const foo = inject("foo")
          return {
            foo
          }
        }
      }
      • 结果:我们会获取到 Middle 组件中提供的 foo 的值 fooTwo,说明子组件提供的数据会覆盖父组件提供的数据
        • 还有一个问题,当我们在 Middle 组件中通过 inject 获取 foo 的值时,会获取到哪个值呢?
        • 结果:我们会获取到 Middle 组件中提供的 foo 的值 fooTwo, 我们应该获取到 Provider 组件中提供的 foo 的值 fooVal,这就不对了
      • 分析:因为我们在 Middle 组件中调用 provide 的时候,是把数据存储在 Middle 组件实例的 provides 对象上,而 Middle 组件实例的 provides 对象是继承自 Provider 组件实例的 provides 对象的,所以当我们在 Consumer 组件中通过 inject 获取 foo 的值时,会先从 Middle 组件实例的 provides 对象上获取,没有再去祖先组件 Provider 组件实例的 provides 对象上获取
        • 而我们在 Middle 组件中通过 inject 获取 foo 的值时,已经被 Middle 组件实例的 provides 对象上的 foo 覆盖了,所以获取到的是 fooTwo
      • 解决方案?
      js 复制代码
      export function provide(key, value) {
        // 存
        const currentInstance:any = getCurrentInstance() // 这个方法仅在 setup 函数中可以使用,所以 provide、inject 也只能在 setup 函数中使用
        if(currentInstance) {
          let { provides } = currentInstance
          const parentProvides = currentInstance.parent.provides
          if(provides === parentProvides) {
            provides = currentInstance.provides = Object.create(parentProvides)  // ✅ 这里我们需要改造一下
          }
          provides[key] = value
        }
      }
      // 效果:实现了祖先组件 provide, 父亲组件也同时 provide, 子组件 inject,拿到父组件里面的对象里面改过的属性,父组件 inject,拿到是祖先组件的 provides 
      provides: {foo: "fooTwo"}
        [[Prototype]]:Object
              bar:"barVal"
              foo:"fooVal"
      • 结果:我们实现了祖孙组件之间的传递,并且解决了子组件提供的数据覆盖父组件提供的数据的问题
      • 回顾:我们 provide 再次存储值,修改了 provides 对象,让 provides 对象原型指向父组件实例的 provides 对象,这样就实现了数据的继承,同时避免了子组件提供的数据覆盖父组件提供的数据的问题。
  1. 我们已经实现了祖孙传值的功能,我们再复杂一点,我们在中间组件 Middle 中 inject 注入 baz,给一个默认值 defaultVal,在页面访问,会展示什么呢?
js 复制代码
const ProviderTwo = {
  name: 'ProviderTwo',
  render() {
    return h('div', {}, [h('p', {}, "ProviderTwo " + this.foo + this.baz), h(Consumer)]) // ✅ 显示 baz
  },
  setup() {
    provide("foo", "fooTwo")
    const foo = inject("foo")
    const baz = inject("baz", "defaultVal") // ✅ 给一个默认值
    return {
      foo,
      baz
    }
  }
}
  • 结果:我们会获取到 undefined
  • 分析:因为我们在 Middle 组件中通过 inject 获取 baz 的值时,发现没有提供 baz,所以会返回 undefined,而不是默认值 defaultVal
  • 解决方案?
js 复制代码
export function inject(key, defaultVal?) {
  // 取
  const currentInstance:any = getCurrentInstance()
  if(currentInstance) {
    const parentProvides = currentInstance.parent.provides
    if(key in parentProvides) {
      return parentProvides[key]
    } else if(defaultVal) {
      return defaultVal
    }
  }
}
  • 结果:我们实现了在中间组件中注入一个不存在的值时,返回默认值的功能
相关推荐
VX:Fegn089517 分钟前
计算机毕业设计|基于springboot + vue酒店管理系统(源码+数据库+文档)
vue.js·spring boot·课程设计
广州华水科技21 分钟前
单北斗GNSS形变监测一体机在基础设施安全中的应用与技术优势
前端
EndingCoder31 分钟前
案例研究:从 JavaScript 迁移到 TypeScript
开发语言·前端·javascript·性能优化·typescript
Irene19911 小时前
Vue3 中使用的命名规则 和 实际开发命名规范总结
vue.js·命名规范
阿珊和她的猫2 小时前
React 路由:构建单页面应用的导航系统
前端·react.js·状态模式
Amumu121382 小时前
Vue脚手架(二)
前端·javascript·vue.js
花间相见2 小时前
【LangChain】—— Prompt、Model、Chain与多模型执行链
前端·langchain·prompt
lichenyang4533 小时前
从零开始构建 React 文档系统 - 完整实现指南
前端·javascript·react.js
比特森林探险记3 小时前
Hooks、状态管理
前端·javascript·react.js
landonVM3 小时前
Linux 上搭建 Web 服务器
linux·服务器·前端