vue3学习随便记9

深入组件

组件注册

注册组件时必须给组件命名。组件名的命名规则和组件要使用在哪里有关,如果只是要混在 DOM 中使用组件,那名字应该全部小写,多个单词用连字符连接(即kebab-case),这样可以避免与 HTML元素发生冲突。如果在字符串模板或单文件组件中定义组件,那么既可以用 kebab-case,也可以用 PascalCase (首字母大写的驼峰式)。当使用后者时,使用组件时,即可以 PascalCase,也可以 kebab-case,但混入 DOM 使用仍然必须 kebab-case。

我们前面用的都是全局注册,即使用 app.component() 函数。和变量的情形一样,一旦全局注册,那么它可以在 app 的模板中使用,也可以在后续组件的模板中使用。

和全局注册相对的是局部注册,即在应用 app 的配置项 components 或者组件的配置项 components 中进行声明(每个组件包含一个组件名作为键,值则是该组件的配置),然后在app范围内或者(父)组件范围内使用。局部注册可以使得构建工具编译时跳过那些并未被实际使用的组件。

const ComponentA = {
  /* ... */
}
const ComponentB = {
  /* ... */
}
const ComponentC = {
  /* ... */
}
const app = Vue.createApp({
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})
const ComponentA = {
  /* ... */
}

const ComponentB = {
  components: {
    'component-a': ComponentA
  }
  // ...
}

当我们使用 Babel 和 webpack 那样的构建工具,按 ES2015+模块方式使用则如下

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  }
  // ...
}

组件文件可以是 ComponentA.vueComponentA.js,导入时,可以不带后缀,即

import ComponentA from './ComponentA'

尽管一般使用构建工具来使用组件,但我们的确可以直接按Javascript模块来使用组件。下面的例子纯粹为了演示,我们把前面的 todo-list 例子改一改。

先编写模块 TodoItem.mjs (模块概念可以参考 JavaScript modules 模块 - JavaScript | MDN,不使用Vue编译构建工具,我们只能使用JS内的字符串模板),TodoItem模块导出 TodoItem对象

const TodoItem = {
    template: `
        
  • { { title }}
  • `, props: ['title'], emits: ['remove'] } export { TodoItem }

    然后编写 TodoList.mjs 模块,该模块从 TodoItem.mjs模块 导入 TodoItem对象,同时自身又导出 TodoList 对象。TodoList 组件内部局部注册了 TodoItem组件

    import { TodoItem } from "./TodoItem.mjs"
    
    const TodoList = {
        components: {
            TodoItem
        },
        data() {
            return {
                newTodoText: '',
                todos: [
                    { id:1, text: 'Learn JavaScript' },
                    { id: 2, text: 'Learn Vue' },
                    { id: 3, text: 'Build something awesome' }
                ],
                nextTodoId: 4
            }
        },
        methods: {
            addNewTodo() {
                this.todos.push({
                    id: this.nextTodoId++,
                    text: this.newTodoText
                })
                this.newTodoText = ''
            }
        }
    }
    
    export { TodoList }

    最后,编写 main.mjs 模块,它引入 TodoList,并作为最后给 HTML 使用的模块

    import { TodoList } from './TodoList.mjs'
    
    Vue.createApp(TodoList).mount('#list-rendering')
    
    
    
        
    
    
    
        

    然后,在这些文件所在目录,启动 Web服务监听某端口 (例如用php命令  php  -S  0.0.0.0:8000),再在浏览器访问 http://localhost:8000/test.html 即可。(不能使用本地加载Html文件的方式来访问,否则会遇到 CORS错误,这是Javascript模块的安全性限制)

    Props

    我们前面是以字符串数组形式列出 prop,从而这些属性的取值类型是没有明确限定的。我们可以以对象形式列出 prop,对象 property的名称和值分别对应 prop的名称和类型:

    props: {
      title: String,
      likes: Number,
      isPublished: Boolean,
      commentIds: Array,
      author: Object,
      callback: Function,
      contactsPromise: Promise // 或任何其他构造函数
    }

    使用组件时,可以给 prop传入静态的值(字符串),如

    更常见的,我们会为 prop 动态绑定变量的值或者表达式的值

    
    
    
    
    

    当传入数字时,我们需要用动态绑定,以明确不是字符串,而是表达式

    
    
    
    
    
    

    传入布尔值,同样如此

    
    
    
    
    
    
    
    
    
    

    传入数组

    
    
    
    
    
    

    传入对象

    
    
    
    
    
    

    如果要把一个对象的 property 都作为 prop 传入,即对于以下对象

    post: {
      id: 1,
      title: 'My Journey with Vue'
    }

    要实现下述绑定效果

    可以有更简单的绑定语法(v-bind = "对象名",对象的每个 property 自动绑定到每个 prop)

    Vue组件 prop 的数据流向是单向的,即从父组件向子组件单向下行绑定:父级 prop 的更新会流动到子组件,导致子组件 prop 刷新为最新的值。用户不应该在子组件内部改变 prop。可能让用户想在子组件内部改变 prop 值的情形有:

    1、想用 prop 传递一个初始值,之后子组件想把它作为一个本地的 prop 数据来使用。例如,一个计算器变量 counter,希望父组件传递一个初始值。这种情形下,不应该把 counter 定义为 prop,应该额外定义一个 initialCounter 作为 prop,counter 作为组件的数据属性变量,并初始化为 initialCounter的值,即

    props: ['initialCounter'],
    data() {
      return {
        counter: this.initialCounter
      }
    }

    2、想用 prop 传递一个值,但这个值需要经过转换才适合子组件使用。这种情况下,最好定义一个计算属性来转换 prop 的值,例如

    props: ['size'],
    computed: {
      normalizedSize() {
        return this.size.trim().toLowerCase()
      }
    }

    说明:在 Javascript 中,数组和对象是通过引用传递的,所以,对于 数组或者对象类型的 prop,如果在子组件中改变这个对象或数组本身将会影响到父组件的状态,这一点也足以说明 prop 单向数据流的必要性。

    因为子组件可能是被他人使用的,所以,对 prop 进行相关的类型和数据验证就是必要的(对子组件开发者来说其实也必要)。

    app.component('my-component', {
      props: {
        // 基础的类型检查 (`null` 和 `undefined` 值会通过任何类型验证)
        propA: Number,
        // 多个可能的类型
        propB: [String, Number],
        // 必填的字符串
        propC: {
          type: String,
          required: true
        },
        // 带有默认值的数字
        propD: {
          type: Number,
          default: 100
        },
        // 带有默认值的对象
        propE: {
          type: Object,
          // 对象或数组的默认值必须从一个工厂函数返回
          default() {
            return { message: 'hello' }
          }
        },
        // 自定义验证函数
        propF: {
          validator(value) {
            // 这个值必须与下列字符串中的其中一个相匹配
            return ['success', 'warning', 'danger'].includes(value)
          }
        },
        // 具有默认值的函数
        propG: {
          type: Function,
          // 与对象或数组的默认值不同,这不是一个工厂函数——这是一个用作默认值的函数
          default() {
            return 'Default function'
          }
        }
      }
    })

    prop 验证发生在组件实例创建之前,因此,default() 函数 或 validator() 函数中无法使用实例的 property (data、computed等)

    验证中的类型检查,type 除了可以是原生构造函数 String、Number、Boolean、Array、Object、Date、Function、Symbol 之一,还可以是自定义构造函数,通过 instanceof 来进行检查确认。例如,给定如下构造函数

    function Person(firstName, lastName) {
      this.firstName = firstName
      this.lastName = lastName
    }

    就可以使用

    app.component('blog-post', {
      props: {
        author: Person
      }
    })

    来验证 author 这个 prop 的值是否是通过 new Person 创建的(实例)。

    prop 的大小写命名规则,同样遵循 HTML 中使用 kebab-case 和 JS 中使用 camelCase (字符串模板则没有限制): JS中 prop postTitle 对应 HTML 中 attribute post-title

    非 prop 的 Attribute

    组件作为自定义元素,我们除了添加在 props 或 emits 有对应定义的 attribute,还可以添加非 prop 的 attribute,常见的非 prop attribute 有 class、style、id。组件内部可以通过实例 property $attrs 来访问到这些 attributes。

    Attribute 继承

    当组件返回的是单个根节点时,非 prop 的 attribute 将自动添加到根节点的 attribute 中。例如,日期选择组件 date-picker 返回单个根节点

    app.component('date-picker', {
      template: `
        
    ` })

    如果我们用 data-status attribute 来表示 组件的状态,即

    data-status attribute 会合并到根节点 div.data-picker,从而渲染为

    对于事件监听器,也有同样的规则。我们下面的例子来演示非prop attribute的合并情况和父组件向子组件传递初始值的情况:

        

    vue3学习随便记9_第1张图片

    对于一个HTML根元素本身具有 change事件的组件来说,给它附加非prop的事件监听器change是有意义的,因为该事件监听器会从父组件传递到子组件,我们不需要显式从 date-picker 用代码去引发事件,因为子组件可以自动触发事件。

        

    禁用 Attribute 继承

    和继承相反,如果不希望组件的根元素继承 attribute,可以在组件选项中设置 inheritAttrs: false,这样组件就不会进行自动合并的操作了。禁用 attribute 自动继承的常见场景是希望将 attribute 应用到异于根元素的其他元素。

    app.component('date-picker', {
      inheritAttrs: false,
      template: `
        
    ` })

    上面的代码,会把外部 attribute 绑定到组件内的 input 上,即把

    渲染为

    多个根节点的 Attribute 继承

    Vue 没有规定多个根节点如何自动实现 attribute 继承,所以,多个根节点时,必须类似禁用Attribute那样手动绑定来实现继承(对某个元素 v-bind="$attrs"),不然会出现运行时警告。

    app.component('custom-layout', {
      template: `
        
    ...
    ...
    ...
    ` })

    你可能感兴趣的:(javascript,前端,开发语言)