vue-day03-vue组件

197 阅读5分钟

一、vue组件基本使用

  • 引入vue.js
<script src="./lib/vue.js"></script>
  • 1.定义组件
const MyCom={
    template:'<div>我是一个组件</div>'
}
  • 2.注册组件
    // 名称定义 
    // 可以是首字母大写 渲染的时候 可以写成小写  
    // 可以是驼峰式 渲染的时候 需要使用连接的方式   
    Vue.component('MyCom', MyCom)
    new Vue({
      el: "#app",
    })

  • 3.渲染
<my-com></my-com>
<My-com></My-com>

二、私有组件

new Vue({
      el: "#app",
      filters: {},
      directives: {},
      // 私有注册组件
      components: {
        MyCom
      }
    })

实例:

<body>
  <div id="app">
    <!-- 3. 渲染组件 -->
    <my-com></my-com>
  </div>

  <template id="template">
    <!-- 组件的模板中有且只能有一个根元素 -->
    <div>
      <div>我是一个组件</div>
      <div>我是一个描述</div>
    </div>
  </template>
  <script>


    // 1. 定义组件  
    const MyCom = {
      template: '#template'
    }

    // const MyCom = Vue.extend({
    //   template: '<div>我是一个组件</div>'
    // })

    // 2. 全局注册组件 
    // Vue.component('MyCom', MyCom)
    new Vue({
      el: "#app",
      filters: {},
      directives: {},
      // 私有注册组件
      components: {
        MyCom
      }
    })


  </script>
</body>

三、组件切换

<body>
  <div id="app">
    <a href="javascript:;" @click="componentId='Login'">login</a>
    <a href="javascript:;" @click="componentId='Register'">Register</a>
    <!-- is 设置要切换的组件名称 -->
    <component :is="componentId"></component>
  </div>
  <script>
    const Login = {
      template: '<div>Login组件 </div>'
    }
    const Register = {
      template: '<div>Register组件 </div>'
    }
    new Vue({
      el: "#app",
      data: {
        flag: false,
        componentId: 'Login'//初始值
      },
      components: {
        Login,
        Register
      }
    })
  </script>
</body>

四、组件切换-动画

<!-- 
      mode  设置动画的模式
        out-in 先出后进
        in-out 先进后出  
     -->
<style>
    .v-enter,
    .v-leave-to {
      opacity: 0;
      transform: translateX(300px)
    }

    .v-leave-active,
    .v-enter-active {
      transition: all .8s ease;
    }
  </style>
  
  <body>
  <div id="app">
    <a href="javascript:;" @click="componentId='Login'">login</a>
    <a href="javascript:;" @click="componentId='Register'">Register</a>
    <!-- 
      mode  设置动画的模式
        out-in 先出后进
        in-out 先进后出  
     -->
    <transition mode="out-in">
      <component :is="componentId"></component>
    </transition>
  </div>
  <script>
    const Login = {
      template: '<div>Login组件 </div>'
    }
    const Register = {
      template: '<div>Register组件 </div>'
    }
    new Vue({
      el: "#app",
      data: {
        componentId: 'Login'
      },
      components: {
        Login,
        Register
      }
    })
  </script>
</body>

五、组件通信-父传子

  • 在渲染的组件上,通过v-bind的方式传递父组件的值
  • 在子组件中,通过props接收传递的数据
<body>
  <div id="app">
    {{msg}}
    <!-- 
      
      第一个msg 是传递给子组件的属性,属性定义不要使用驼峰式的
      
      第二个msg 是父组件的值
    -->
    <Login :msg="msg"></Login>
  </div>
  <script>
    // 每个组件的数据是独立的  
    const Login = {
      props: ['msg'],
      template: '<div>login组件---{{msg}}</div>',
    }
    // 根组件
    new Vue({
      el: "#app",
      data: {
        msg: '父组件的数据'
      },
      components: {
        Login
      }
    })
  </script>
</body>

六、组件通信-子传父

  • 在渲染的组件上,通过v-on的方式传递父组件的函数
  • 在子组件中,通过$emit() 调用传递的函数,调用的同时可以传递子组件的参数
<body>
  <div id="app">
    {{msg}}
    <Login :msg="msg" @click-left="getSon"></Login>
  </div>
  <script>
    // 每个组件的数据是独立的  
    const Login = {
      // 父组件传递过来的数据是只读  
      // props: ['msg'],
      props: {
        msg: {
          type: String,
          default: '默认值'
        }
      },
      template: '<div>login组件--{{msg}}</div>',
      created() {
        this.$emit('click-left', '子组件的数据')
        // this.msg = 'ssr'
        // console.log(this.msg)
      }
    }
    // 根组件
    new Vue({
      el: "#app",
      data: {
        msg: '父组件的数据'
      },
      components: {
        Login
      },
      methods: {
        getSon(data) {
          // console.log(data)
          this.msg = data
        }
      }
    })
  </script>
</body>

七、组件通信-兄弟传值

B ——————A

  1. 定义一个空的Vue实例 Bus eventBus

  2. 在组件 A 中,使用$on 监听 定义函数

  3. 在组件B中,使用$emit 调用 A的函数,调用的同时传递数据

<body>
 <div id="app">
   <com-a></com-a>
   <com-b></com-b>
 </div>
 <script>
   const Bus = new Vue()
   const ComA = {
     template: '<div>ComA</div>',
     created() {
       Bus.$on('getA', function (data) {
         console.log(data)
       })
     }
   }

   const ComB = {
     template: '<div>ComB <button @click="setData">传递数据</button></div>',
     methods: {
       setData() {
         Bus.$emit('getA', '组件b的数据')
       }
     }
   }
   new Vue({
     el: "#app",
     data: {

     },
     components: {
       ComA,
       ComB
     },
     created() {
     },
     methods: {
     }
   })
 </script>
</body>

八、组件通信-ref

  • 作为获取子组件数据
  • 作为获取元素的DOM
  • 如果拿不到DOM 可以配合 this.$nextTick 一起使用
<body>
 <div id="app">
   <div id="box" ref="boxRef">box</div>
   <Login ref="loginRef"></Login>
 </div>
 <script>
   // 每个组件的数据是独立的  
   const Login = {
     template: '<div>login组件---{{msg}}</div>',
     data() {
       return {
         msg: '子组件数据'
       }
     },
     methods: {
       show() {
         console.log('子组件的函数')
       }
     }
   }
   // 根组件
   new Vue({
     el: "#app",
     data: {
       msg: '父组件的数据'
     },
     components: {
       Login
     },
     // 只执行一次
     created() {
       // console.log(this.$refs)
       console.log(document.getElementById('box'))
       // 监听所有的DOM执行完毕之后调用
       this.$nextTick(() => {
         console.log(this.$refs.boxRef)
       })
     },
     // 只执行一次
     mounted() {
       // console.log(this.$refs.loginRef)
       // console.log(this.$refs.loginRef.msg)
       // this.$refs.loginRef.show()
       console.log(this.$refs.boxRef)
     }
   })
 </script>
</body>

九、为什么子组件中的data是一个函数

  • 因为如果data不是函数的话,改变一个值将改变所有值,地址一样(栈),如果是函数的话,改变一个值不会影响其他值(堆)
<body>
 <div id="app">
   <Login></Login>
   <Login></Login>
   <Login></Login>
   <Login></Login>
   <Login></Login>
   <Login></Login>
 </div>
 <script>
   // const obj = {
   //   msg: 0
   // }
   const Login = {
     // es6  
     // data() {
     //   return {
     //     msg: 0
     //   }
     // },
     // es5 
     // data: function () {
     //   return {
     //     msg: 0
     //   }
     // },
     // 箭头函数写法  
     data: () => ({
       msg: 0
     }),
     template: '<div>login组件---{{msg}} <button @click="msg++">+</button></div>',
   }
   // 根组件
   new Vue({
     el: "#app",
     data: {
       msg: '父组件的数据'
     },
     components: {
       Login
     }
   })
 </script>
</body>

十、匿名插槽

<body>
 <div id="app">

   <Login>
     <div>
       <h1>logo</h1>
       <span>描述</span>
     </div>
   </Login>
 </div>
 <script>

   const Login = {
     template: '<div><slot>login组件</slot></div>',
   }

   new Vue({
     el: "#app",
     components: {
       Login
     }
   })
 </script>
</body>

十一、具名插槽

<body>
 <div id="app">
   <Login>
     <div slot="header">
       <h1> this is header</h1>
       <div>desc</div>
     </div>

     <div slot="main">
       this is main
     </div>
     <div slot="footer">
       this is footer
     </div>
   </Login>
 </div>
 <script>

   const Login = {
     template: `
       <div>
         <slot name="header">header</slot>  
         <slot name="main">main</slot>  
         <slot name="footer">footer</slot>  
       </div>
     `
   }

   new Vue({
     el: "#app",
     components: {
       Login
     }
   })
 </script>
</body>

十二、作用域插槽

<body>
 <div id="app">
   <Login>
     <template slot-scope="scope">
       <strong v-if="scope.item === 'red'">{{scope.item}}</strong>
       <span v-else>{{scope.item}}</span>
     </template>
   </Login>
 </div>
 <script>

   const Login = {
     data: () => {
       return {
         list: ['green', 'red', 'blue']
       }
     },
     template: `
       <div>
         <p v-for="item in list" :key="item">
           <slot :item="item">{{item}}</slot>
         </p>
       </div>
     `
   }
   new Vue({
     el: "#app",
     components: {
       Login
     }
   })
 </script>
</body>

十三、v-slot

<body>
 <div id="app">
   <Login>
     <!-- <div slot="title"> login 组件</div> -->
     <!-- <template v-slot:title="scope"> -->
     <template #title="scope">
       <div>{{ scope.info}}</div>
     </template>
   </Login>
 </div>
 <script>

   const Login = {
     data: () => {
       return {
         list: ['green', 'red', 'blue']
       }
     },
     template: `
       <div>
        <slot name="title" :info="'green'"> login 组件</slot>
       </div>
     `
   }
   new Vue({
     el: "#app",
     components: {
       Login
     }
   })
 </script>
</body>

总结

  1. 组件是独立和可复用的代码组织单元。组件系统是vue核心特性之一,它使开发者使用小型、独立和通常可复用的组件构建大型应用
  2. 组件花开发能大幅提高应用的开发效率、测试性、复用性等
  3. 组件使用按分类有:页面组件、业务组件、通用组件
  4. vue的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生炒其构造函数,它们基于VueComponent,扩展于Vue
  5. vue中常见组件化技术有: 属性prop,自定义事件,插槽等,它们主要用于组件通信、扩展等
  6. 合理的划分组件,有助于提升应用性能
  7. 组件应该是高内聚、低耦合
  8. 遵循单向数据流的原则

记单词

vue

methods 函数

data 数据

model 数据模型

view 视图模型

created 创建

mounted 挂载

updated 更新

destroyed 销毁

template 模板

filter 过滤

directive 指令

expression 表现

bind 绑定

binding

resource 资源

axios

insert 插入

duration 持续时间

作业:用组件实现下图布局以及添加、删除、点击修改功能

image.png

  • 1.布局 定义子组件
const TodoHeader = {
      props: {//使用props接收父组件传来的值(父传子)
        title: {
          type: String,
          default: '默认值'
        }
      },
      template: `
        <div>
          <slot>
            <h1>{{title}}</h1>
            <hr />  
          </slot>
        </div>
      `
    }
    const TodoInput = {
      props: {
        txt: {
          type: String,
          default: '添加'
        }
      },
      template: `
        <div>
          <input type="text" />  
          <input type="button" :value="txt"/>  
        </div>
      `
    }
    const TodoList = {
      props: ['todos'],
      template: `
        <div>
          <ul>
            <li v-for="todo in todos" :key="todo.id">
              <input type="checkbox" :checked="todo.completed"/>  
              <span>{{todo.title}}</span>
              <span>{{todo.completed?'已完成❤️':'未完成🖤'}}</span>
              <button>删除</button>
            </li>  
          </ul>  
        </div>
      `
    }
    const TodoFooter = {
      template: `
        <div>
          <button>全部</button>  
          <button>已完成</button>  
          <button>未完成</button>  
        </div>
      `
    }

(2)注册子组件

//父组件
    new Vue({
      el: '#app',
      data: {
        msg: '待办事项',
        text: 'ADD',
        todos: [
          {
            id: 0,
            title: '上课',
            completed: true
          },
          {
            id: 1,
            title: '睡觉',
            completed: false
          },
          {
            id: 2,
            title: '下课',
            completed: false
          },
        ]
      },
      created() { },
      methods: {},
      components: {//2.注册
        TodoHeader,
        TodoInput,
        TodoList,
        TodoFooter
      }
    })

(3)渲染到页面上

<div id="app">
<!-- :title是要改变的子组件的属性  msg是父组件的属性值 -->
    <todo-header :title="msg"></todo-header>
    <todo-input :txt="text"></todo-input>
    <todo-list :todos="todos"></todo-list>
    <todo-footer></todo-footer>
  </div>
  • 2.实现添加功能 (1)在父组件上写一个添加函数
methods: {
        addTodo(title) {
          // console.log(title);//输入框中的内容
          this.todos.push({//添加对象
            id: new Date().getTime(),
            title,
            completed: false
          })
        },
      },

(2)在渲染页面上定义事件触发“添加函数”

<todo-input :txt="text" @click="addTodo"></todo-input>

(3)子组件上:

const TodoInput = {
      props: {
        txt: {
          type: String,
          default: '添加'
        }
      },
      data: () => ({
        value: ''
      }),
      template: `
        <div>
          <input type="text" v-model="value" ref="inputRef" @keyup.enter="handleAddTodo"/>  
          <input type="button" :value="txt" @click="handleAddTodo"/>  
        </div>
      `,
      methods: {
        handleAddTodo() {
          this.$emit('click', this.value)//子传父,这里的‘click’是渲染页面中定义的‘click’用来触发addTodo()函数
          this.value = ''
          this.$refs.inputRef.focus()
        }
      }
    }
  • 3.实现编辑功能(点击checkbox,改变内容,即未完成变为已完成) (1)在父组件上添加编辑函数
changeTodo(id) {
          // console.log(id);
          this.todos.forEach(todo => {
            if (todo.id === id) {
              todo.completed = !todo.completed
            }
          })
        },

(2)渲染页面上定义chenge事件用来触发chengeTodo函数

<todo-list :todos="todos" @change="changeTodo"></todo-list>

(3)子组件上

const TodoList = {
      props: ['todos'],
      template: `
        <div>
          <ul>
            <li v-for="todo in todos" :key="todo.id">
              <input type="checkbox" :checked="todo.completed" @click="handleChangeTodo(todo.id)"/>
              <span>{{todo.title}}</span>
              <span>{{todo.completed?'已完成❤️':'未完成🖤'}}</span>
              <button>删除</button>
            </li>
          </ul>   
        </div>
      `,
      methods: {
        handleChangeTodo(id) {
          this.$emit('change', id)
        }
      }
    }
  • 4.删除 (1)在父组件上添加编辑函数
deleteTodo(id) {
          console.log(id);
          //根据id获取索引
          let index = this.todos.findIndex(todo => todo.id === id)
          this.todos.splice(index, 1)
        }

(2)渲染页面上定义chenge事件用来触发chengeTodo函数

<todo-list :todos="todos" @change="changeTodo" @delete-todo="deleteTodo"></todo-list>

(3)子组件上

const TodoList = {
      props: ['todos'],
      template: `
        <div>
          <ul>
            <li v-for="todo in todos" :key="todo.id">
              <input type="checkbox" :checked="todo.completed" @click="handleChangeTodo(todo.id)"/>
              <span>{{todo.title}}</span>
              <span>{{todo.completed?'已完成❤️':'未完成🖤'}}</span>
              <button @click="handleDeleteTodo(todo.id)">删除</button>
            </li>
          </ul>   
        </div>
      `,
      methods: {
        handleChangeTodo(id) {
          this.$emit('change', id)
        },
        handleDeleteTodo(id) {
          this.$emit('delete-todo', id)
        }
      }
    }
  • 5.抽离todoitem组件 兄弟传值 B(Bus) ——————A(原来的父组件,即Vue)

    1. 定义一个空的Vue实例 Bus eventBus

    2. 在组件 A 中,使用$on 监听 定义函数

    3. 在组件B中,使用$emit 调用 A的函数,调用的同时传递数据 (1)定义一个空的Vue实例 Bus

const Bus = new Vue()

(2)在组件 A 中,使用$on 监听 定义函数

new Vue({
      el: '#app',
      data: {
        msg: '待办事项',
        text: 'ADD',
        todos: [
          {
            id: 0,
            title: '上课',
            completed: true
          },
          {
            id: 1,
            title: '睡觉',
            completed: false
          },
          {
            id: 2,
            title: '下课',
            completed: false
          },
        ]
      },
      created() {//使用$on 监听 定义函数 
        Bus.$on('changeTodo', (id) => {
          this.todos.forEach(todo => {
            if (todo.id === id) {
              todo.completed = !todo.completed
              console.log(todo.completed);
            }
          })
        })
        Bus.$on('deleteTodo', (id) => {
          console.log(id);
          //根据id获取索引
          let index = this.todos.findIndex(todo => todo.id === id)
          this.todos.splice(index, 1)
        })
      },
      methods: {
        addTodo(title) {
          // console.log(title);//输入框中的内容
          this.todos.push({//添加对象
            id: new Date().getTime(),
            title,
            completed: false
          })
        }
      },
      components: {//2.注册
        TodoHeader,
        TodoInput,
        TodoList,
        TodoFooter
      }
    })

(3)在组件B中,使用$emit 调用 A的函数,调用的同时传递数据

  • 定义一个新组件TodoItem
const TodoItem = {
      props: ['todo'],
      template: `
            <li>
              <input type="checkbox" :checked="todo.completed" @click="handleChangeTodo(todo.id)"/>
              <span>{{todo.title}}</span>
              <span>{{todo.completed?'已完成❤️':'未完成🖤'}}</span>
              <button @click="handleDeleteTodo(todo.id)">删除</button>
            </li>
      `,
      methods: {
        handleChangeTodo(id) {
          Bus.$emit('changeTodo', id)
        },
        handleDeleteTodo(id) {
          Bus.$emit('deleteTodo', id)
        }
      }
    }

  • 修改原来的TodoList组件
const TodoList = {
      props: ['todos'],
      template: `
        <div>
          <ul>
            <TodoItem v-for="todo in todos" :key="todo.id" :todo="todo"></TodoItem>
          </ul>   
        </div>
      `,
      components: {
        TodoItem
      }
    }
  • 渲染页面
<div id="app">
    <!-- :title是要改变的子组件的属性  msg是父组件的属性值 -->
    <todo-header :title="msg"></todo-header>
    <todo-input :txt="text" @click="addTodo"></todo-input>
    <todo-list :todos="todos"></todo-list>
    <todo-footer></todo-footer>
  </div>
  • 6.抽离TodoItem组件+插槽 (1)修改TodoItem组件和TodoList组件
const TodoItem = {
      props: ['todo'],
      template: `
            <li>
              <slot></slot>
            </li>
      `
    }

    const TodoList = {
      props: ['todos'],
      template: `
        <div>
          <ul>
            <slot></slot>
          </ul>   
        </div>
      `
    }

(2)父组件方法

methods: {
        changeTodo(id) {
          this.todos.forEach(todo => {
            if (todo.id === id) {
              todo.completed = !todo.completed
              console.log(todo.completed);
            }
          })
        },
        deleteTodo(id) {
          console.log(id);
          //根据id获取索引
          let index = this.todos.findIndex(todo => todo.id === id)
          this.todos.splice(index, 1)
        },
        addTodo(title) {
          // console.log(title);//输入框中的内容
          this.todos.push({//添加对象
            id: new Date().getTime(),
            title,
            completed: false
          })
        }
      },

(3)注册TodoItem组件

 components: {//2.注册
        TodoHeader,
        TodoInput,
        TodoList,
        TodoFooter,
        TodoItem
      }

(4)渲染页面

<div id="app">
    <!-- :title是要改变的子组件的属性  msg是父组件的属性值 -->
    <todo-header :title="msg"></todo-header>
    <todo-input :txt="text" @click="addTodo"></todo-input>
    <todo-list>
      <todo-item v-for="todo in todos" :key="todo.id">
        <input type="checkbox" :checked="todo.completed" @click="changeTodo(todo.id)" />
        <span>{{todo.title}}</span>
        <span>{{todo.completed?'已完成❤️':'未完成🖤'}}</span>
        <button @click="deleteTodo(todo.id)">删除</button>
      </todo-item>
    </todo-list>
    <todo-footer></todo-footer>
  </div>
  • 7.真实接口 (1)创建一个data.json文件,里面放置所需数据,创建完后开启
  • data.json
{
  "todos": [
    {
      "id": 2,
      "title": "下课",
      "completed": true
    },
    {
      "id": 3,
      "title": "上课",
      "completed": true
    },
    {
      "id": 4,
      "title": "睡觉",
      "completed": false
    },
    {
      "id": 1622704051897,
      "title": "上课",
      "completed": false
    },
    {
      "id": 1622704057049,
      "title": "听音乐",
      "completed": false
    }
  ]
}
  • 开启data.json数据接口
$ json-server --watch ./data.json

(2)在父组件中定义getTodo函数

async getTodos() {
          const res = await axios.get('http://127.0.0.1:3000/todos')
          this.todos = res.data
        },

(3)其他函数改为异步函数

async addTodo(title) {
          await axios.post('http://127.0.0.1:3000/todos', {
            id: new Date().getTime(),
            title,
            completed: false
          })
        },
        async changeTodo(id, completed) {
          // let count = 0
          // this.todos.forEach((todo, index) => {
          //   if (todo.id === id) {
          //     todo.completed = !todo.completed
          //     count = index
          //   }
          // });
          // await axios.put('http://127.0.0.1:3000/todos' + id, this.todos[count])

          await axios.patch('http://127.0.0.1:3000/todos/' + id, { completed: !completed })
          this.getTodos()
        },

        async deleteTodo(id) {
          // console.log(id)
          //根据id获取索引
          // let index = this.todos.findIndex(todo => todo.id === id)
          // this.todos.splice(index, 1)
          await axios.delete('http://127.0.0.1:3000/todos/' + id)
          this.getTodos()
        },
        getAll() {
          this.getTodos()
        },
        

(4)TodoFooter组件函数

async getTrue() {//已完成
          await this.getTodos()
          let newTodos = []
          this.todos.forEach(todo => {
            if (todo.completed === true) {
              newTodos.push(todo)
            }
          })
          this.todos = newTodos
        },
        async getFalse() {//未完成
          await this.getTodos()
          let newTodos = []
          this.todos.forEach(todo => {
            if (todo.completed === false) {
              newTodos.push(todo)
            }
          })
          this.todos = newTodos
        }

(5)TodoList组件与TodoFooter组件

const TodoList = {
      template: `
        <div>
          <ul>
              <slot></slot>
          </ul>  
        </div>
      `,
      methods: {
        handleChangeTodo(id) {
          this.$emit('change', id)
        },
        handleDeleteTodo(id) {
          this.$emit('click-delete', id)
        }
      }
    }
    const TodoFooter = {
      template: `
        <div>
          <button @click="$emit('click-all')">全部</button>  
          <button @click="$emit('click-true')">已完成</button>  
          <button @click="$emit('click-false')">未完成</button>  
        </div>
      `,
      methods: {
        // getAll() {
        //   this.$emit('click-all')
        // },
        // getTrue() {
        //   this.$emit('click-true')
        // },
        // getFalse() {
        //   this.$emit('click-false')
        // }
      }
    }

(6)渲染页面

<div id="app">
    <todo-header :title="title"></todo-header>
    <todo-input :txt="txt" @click="addTodo"></todo-input>
    <todo-list :todos="todos" @change="changeTodo" @click-delete="deleteTodo">
      <todo-item v-for="todo in todos" :key="todo.id">
        <input type="checkbox" :checked="todo.completed" @click="changeTodo(todo.id,todo.completed)">
        <span>{{todo.title}}</span>
        <span>{{todo.completed?'已完成(*^▽^*)':'未完成o(╥﹏╥)o'}}</span>
        <button @click="deleteTodo(todo.id)">删除</button>
      </todo-item>
    </todo-list>
    <todo-footer @click-all="getAll" @click-true="getTrue" @click-false="getFalse"></todo-footer>
  </div>