尚硅谷Vue技术全家桶(2)

2.vue组件化编程

2.1模块与组件,模块化与组件化

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
组件定义:
实现应用中局部功能代码(html,css,js)和资源(mp3,mp4)的集合。

2.1.1 模块

在这里插入图片描述

2.1.2 组件

在这里插入图片描述

2.1.3 模块化

在这里插入图片描述

2.1.4 组件化

在这里插入图片描述

2.2 非单文件组件

2.2.1 初识

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
  <div id="root1">
    <school></school>
    <hr>
    <student></student>
  </div>

  <div id="root2">
    <hello></hello>
  </div>
</body>



  <script type="text/javascript">
    
    Vue.config.productionTip = false;

    // 创建school组件
    const school=Vue.extend({
      template:`
      <div>
        <h1>schoolInfo</h1>
        <h2>name:{{schoolName}}</h2>
        <h2>addr:{{schoolAddress}}</h2>
        <button @click='btn'>点我提示学校名</button>
      </div>
      `,
      // 不写el,最终所有组件被一个vm管理,由vm决定服务于哪个容器
      // el:'#root1',
      data(){
        return {
          schoolName:'atguigu',
          schoolAddress:'beijing'
        }
      },
      methods:{
        btn(){
          alert(this.schoolName)
        }
      }  
    })
    // 创建student组件
    const student=Vue.extend({
      template:`
      <div>
        <h1>studentInfo</h1>
        <h2>name:{{studentName}}</h2>
        <h2>age:{{studentAge}}</h2>
      </div>
      `,
      data(){
        return {
          studentName:'coderhao',
          studentAge:25
        }
      }  
    })
    // 全局组件
    const hello=Vue.extend({
      template:`
      <div>
        <h1>hello</h1>
      </div>
      `,
      // 不写el,最终所有组件被一个vm管理,由vm决定服务于哪个容器
      // el:'#root1',
      data(){
        return{} ;
      },
      methods:{
      }  
    })
    Vue.component('hello',hello)
    new Vue({
      el:'#root1',
      // 组件注册(局部注册)
      components:{
        school:school,
        // 简写
        student
      }
        
    })
    new Vue({
      el:'#root2',
    })
  </script>
</html>

2.2.2 总结

在这里插入图片描述

2.2.3组件嵌套

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
  <div id="root1">
    <!-- 可以在vm里写标签 -->
    <!-- <app></app> -->
  </div>
</body>



  <script type="text/javascript">
  
    Vue.config.productionTip = false;
    const student={
      template:`
        <div>
          <h1>studentInfo</h1>
          <h2>name:{{name}}</h2>
          <h2>age:{{age}}</h2>
        </div>
      `,
      data(){
        return{
          name:'coderhao',
          age:25,
        }
      }
    };
    const school={
      template:`
        <div>
          <h1>schoolInfo</h1>
          <h2>name:{{schoolName}}</h2>
          <h2>addr:{{achoolAddr}}</h2>
          <student></student>
        </div>
      `,
      data(){
        return{
          schoolName:'atguigu',
          achoolAddr:'beijing',
        }
      },
      components:{
        student
      }
    }
    const hello={
      template:`
      <div>
        <h2>{{msg}}</h2>
      </div>
      `,
      data(){
        return{
          msg:'welcome to atguigu'
        }
      }
    }
    const app={
      template:`
        <div>
          <hello></hello>
          <school></school>
        </div>
      `,
      components:{
        hello,
        school
      }
    }
    new Vue({
      el:'#root1',
      data:{
      
      },
      components:{
        app
      },
      template:`<app></app>`
      
  })
  </script>
</html>

在这里插入图片描述

2.2.4 VueComponent

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
  <div id="root1">
    <app></app>
  </div>
</body>

  <template id='schoolTemplate'>
    <div>
      <h1>schoolInfo</h1>
      <h2>name:{{schoolName}}</h2>
      <h2>addr:{{schoolAddr}}</h2>
    </div>
  </template> 
  <template id="appTemplate">
    <div>
      <school></school>
    </div>
  </template>

  <script type="text/javascript">
  
    Vue.config.productionTip = false;
    const school=Vue.extend({
      template:schoolTemplate,
      data(){
        return{
          schoolName:'atguigu',
          schoolAddr:'beijing'
        }
      }
    })
    
    const app={
      template:appTemplate,
      components:{
        school
      }
    }
    // @ ƒ VueComponent (options) {this._init(options);}
    console.log('@',school);
    new Vue({
      el:'#root1',
      data:{
        
      },
      components:{
        app
      }
      
  })
  </script>
</html>

2.2.5 一个重要的内置关系

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
  <div id="root1">
    
  </div>
</body>



  <script type="text/javascript">
    
    Vue.config.productionTip = false;
    // 定义一个构造函数
    function Demo(){
      this.a=1
      this.b=2
    }
    // 创建一个Demo的实例对象
    const d=new Demo()

    // 显式原型属性
    console.log(Demo.prototype);
    // 隐式原型属性
    console.log(d.__proto__);
    //通过显式原型属性操作原型对象,追加x=99
    Demo.prototype.x=99
    console.log(d);
    console.log(d.x);

    const vc=Vue.extend({})
    new Vue({
      el:'#root1',
      data:{
        
      }
        
    })
    //true
    console.log(vc.prototype.__proto__===Vue.prototype);
    
  </script>
</html>

实例的隐式原型属性,永远指向自己缔造者的原型对象:
在这里插入图片描述

2.3 单文件组件

注:
单单词.vue文件名形式:大写开头
多单词.vue文件名形式:1.使用大驼峰 2.小写加短斜杠组合

思路:index.html->main.js->App.vue->Student.vue->School.vue
index.html:组件所在的html页面:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>单文件开发demo</title>
</head>
<body>
  <div id="root">
    
  </div>


  <script src="../js/vue.js"></script>
  <script src="./main.js"></script>
</body>
</html>

main.js:创建Vue实例的js页面:

import App from './App.vue'
new Vue({
  el:'#root',
  template:`<app></app>`,
  components:{
    App
  }
})

App.vue:父组件:

<template>
  <div class="app">

    <Student></Student>
  </div>
</template>

<script>

  import Student from './Student.vue';
  export default {
    name:'App',
    components:{
      Student
    }
  }
</script>

<style>
  .app{
    background-color: coral;
  }
</style>


Student.vue:App.vue的子组件

<template>
  <div class="student">
    <h1>studentInfo</h1>
    <h2>name:{{name}}</h2>
    <h2>age:{{age}}</h2>
    <School></School>
  </div>
</template>

<script>
import School from './School.vue'
export default {
  name:'Student',
  data(){
    return{
      name:'coderhao',
      age:25
    }
  },
  components:{
    School
  }
}
</script>

<style>
  .student{
    background-color: blueviolet;
  }
</style>

School.vue:Student.vue的子组件:

<template>
  <!-- 组件结构 -->
  <div class='school'>
    <h1>schoolInfo</h1>
    <h2>name:{{name}}</h2>
    <h2>addr:{{addr}}</h2>
    <button @click="showName">alert(this.name)</button>
  </div>
  
</template>

<script>
  // 组件交互代码
  export default {
    name:'School',
    data(){
      return{
        name:'atguigu',
        addr:'beijing'
      }
    },
    methods:{
      showName(){
        alert(this.name)
      }
    }
  }

  
</script>

<style>
  /* 组件样式 */
  .school{
    background-color: aqua;
  }
</style>

这里只是一个演示demo,运行index.html是不奏效的,因为浏览器解析不了es6的导入导出组件等操作,需要webpack或者vuecli(脚手架)来处理,转换成浏览器懂的基本的js,css,html。
关于脚手架知识,下面就详细说明。

3. 使用Vue脚手架(VueCLI)

3.1 初始化脚手架

3.1.1 说明

在这里插入图片描述
文档:https://cli.vuejs.org/zh/

3.1.2 安装步骤

1.(仅第一次执行)全局安装vuecli

npm install -g @vue/cli

注:npm配成淘宝镜像,不然vuecli下载特别缓慢

npm config set registry https://registry.npm.taobao.org

重新打开cmd,输入vue命令,如下所示就安装成功了vuecli:
在这里插入图片描述
2.cd到需要项目所在的目录,然后创建项目(这里项目名为cli_test):

vue create cli_test

3.按照说明启动项目
在这里插入图片描述
在这里插入图片描述
可以看到cli准备的一个helloworld项目:
在这里插入图片描述
停止项目就控制台ctrl+c

3.1.3分析cli_test

外部文件:
在这里插入图片描述
main.js:
在这里插入图片描述
app.vue:
在这里插入图片描述
index.html:
在这里插入图片描述

3.1.4 将2.3中的代码替换helloworld,看能否成功显示

替换app.vue,加入Student.vue和School.vue,重新 npm run serve :
在这里插入图片描述
显示成功!
同时由于cli是热部署的,随时改变随时ctrl+s都会重新自动 npm run serve.

3.1.5 分析3.1.3中app.vue遗留的render代码

在这里插入图片描述
所以使用render代替了template的写法。只有创建vm的时候有这个操作,组件里面的template标签有别的东西帮忙解析了。

3.1.6 脚手架修改默认配置

红色的不能改,粉色的可以改:
在这里插入图片描述
能改的项官网已经说清楚了,除此之外的不能改:
https://cli.vuejs.org/zh/config/
在这里插入图片描述
配置文件是vue.config.js,针对这个文件的修改,生效需要重新运行npm run serve.
教学阶段关闭语法检查:
vue.config.js:

module.exports = {
  lintOnSave: false
}

3.1.7 小总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这样在热部署教学的时候不会因为js里对象没被使用等奇怪的问题编译报错。

3.2 ref与props

ref

在这里插入图片描述
App.vue:

<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button @click="logDOM" ref="btn">click me log DOM element</button>
    <School ref="sch"></School>
  </div>
</template>

<script>
  import School from './components/School.vue'
  export default {
    name:'App',
     components:{
      School
    },
    data(){
      return{
        msg:'welcome to learn vue'
      }
    },
    methods:{
      logDOM(){
        console.log(this.$refs.title);//真实DOM
        console.log(this.$refs.btn);//真实DOM
        console.log(this.$refs.sch);//School组件的组件实例对象(VC)
      }
    }
  }
</script>

<style>

</style>

props: 数据传递,父传子

在这里插入图片描述

在这里插入图片描述
App.vue:

<template>
  <div>
    <h1 v-text="msg"></h1>
    <!-- 通过data的过渡,表单里面的改变影响不到数据呈现,但如果不过渡直接使用传入的参数,肯定是影响的 -->
    studentName:<input v-model="student.name"><br>
    studentAge:<input type="number" v-model.number="student.age"><br>
    schoolName:<input v-model="student.school.name"><br>
    schoolAddr:<input v-model="student.school.addr"><br>
    <Student :student='student'></Student>
  </div>
</template>

<script>
  import Student from './components/Student.vue'
  export default {
    name:'App',
     components:{
      Student
    },
    data(){
      return{
        msg:'welcome to learn vue',
        student:{
          name:'coderhao',
          age:25,
          school:{
            name:'atguigu',
            addr:'beijing'
          }
        }
      }
    },
    methods:{
    }
  }
</script>

<style>

</style>

Student.vue:

<template>
  <div>
    <div>
      <h1>{{title}}</h1>
      <h2>name:{{name}}</h2>
  
      <!-- <h2>age:{{this.student.age}}</h2> -->
      <h2>age:{{age}}</h2>
      <button @click="increaseAge">age++</button>
      <!-- <School :school='school'></School> -->
      <!-- 不传参,使用默认参数 -->
      <School ></School>
    </div>
  </div>
  
</template>

<script>
import School from './School.vue'
  export default {
    name:'Student',
    components:{
        School
    },
    data(){
      return{
        title:'studentInfo',
        name:this.student.name,
        age:this.student.age,
        school:this.student.school
      }
    },
    props:['student'],
    methods:{
      increaseAge(){
        
        //最好不要操作源数据,请操作数据的备份
        // this.student.age++,
        this.age++
      }
    }
    
  }
</script>

<style>

</style>

School.vue:

<template>
  <div class="school">
    <h1>{{title}}</h1>
    <h2>name:{{name}}</h2>
    <h2>addr:{{addr}}</h2>
  </div>
</template>

<script>
  export default {
    name:'School',
    data(){
      return{
        title:'schoolInfo',
        name:this.school.name,
        addr:this.school.addr
      }
    },
    // 简单接收
    // props:['name','addr'] 

    // 类型限制接收
    // props:{
    //   name:String,
    //   addr:String
    // },

    // 最详细设置:类型限制+默认值+必要性限制
    // props:{
    //   name:{
    //     type:String,
    //     required: true
    //   },
    //   addr:{
    //     type:String,
    //     required:false,//不是必须传过来的数据
    //     default:'shanghai'//没有数据时的默认值
    //   }
    // }

    props:{
      school:{
        type: Object,
        required:false,
        // Props with type Object/Array must use a factory function to return the default value.
        default(){
          return{
            name:'atguigu',
            addr:'beijing'
          }
          
        }
      }
    }
    
  }
</script>

<style>
  .school{
    background-color: aqua;
  }
</style>

3.3 混入:mixin

在这里插入图片描述
定义混合:
在这里插入图片描述
局部使用:
在这里插入图片描述
全局使用,所有的vc和vm带混合的代码:
在这里插入图片描述

3.4 插件

在这里插入图片描述
plugins.js:

export default{
  install(Vue){
    console.log('install()');
    console.log(Vue);

    // 可以在这里写全局过滤器和全局自定义组件,全局混入,Vue原型上添加方法(vm,vc都能用)等。。。
    // Vue.filter
    // Vue.directive
    // Vue.mixin({})
    // Vue.prototype.hello=()=>{alert('hello !')}
  }
}

main.js:

import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip=false


//引入插件
import plugins from './plugins'
// 使用插件
Vue.use(plugins)

new Vue({
  el:'#app',
  render:h=>h(App)
})

插曲:style标签上的scoped、lang属性

scoped

在这里插入图片描述
在多个组件引入同一个.vue文件里面时,样式名难免冲突,会导致样式冲突,而在组件的style标签上添加scoped就会把样式限制在本vue的template那里。
在这里插入图片描述
App.vue里面的样式基本上很多组件都在用,所以一般不加scoped,所有组件都会用的App.vue的样式。

lang

在这里插入图片描述
代表style是用什么语法写的,css,less之类的,默认css
less编译不了,要装less-loader

3.5 todo-list案例

案例实现预期:
在这里插入图片描述
(本3.5章节最后有整体代码)

前端组件化编码流程:

在这里插入图片描述

1.实现静态组件

即template和css的实现。

2.展示动态数据

数据放到App中,通过props给List数据,通过props一个方法,得到header的返回的数据。
header中输入的东西,可以添加到list中:
在这里插入图片描述

3.交互,绑定事件监听

@click等等。。。。

小总结

在这里插入图片描述

代码:

App.vue:

<template>
  <div>
    <div id="root">
      <div class="todo-container">
        <div class="todo-wrap">
          <MyHeader :addTodo="addTodo"></MyHeader>
          <MyList
            :todos="todos"
            :changeDone="changeDone"
            :removeTodo="removeTodo"
          ></MyList>
          <MyFooter
            :todos="todos"
            :removeTodo="removeTodo"
            :checkAllTodo="checkAllTodo"
          ></MyFooter>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
  name: "App",
  components: {
    MyHeader,
    MyList,
    MyFooter,
  },
  data() {
    return {
      todos: [
        { id: "001", title: "eat", done: true },
        { id: "002", title: "sleep", done: false },
        { id: "003", title: "play", done: false },
      ],
    };
  },
  methods: {
    // 给MyHeader传的方法,MyHeader调用时会触发
    addTodo(todoObj) {
      this.todos.unshift(todoObj);
    },
    // 单个todo选择or不选择方法
    changeDone(todoId) {
      this.todos.forEach((todo) => {
        if (todo.id === todoId) {
          todo.done = !todo.done;
        }
      });
    },
    // 移除todo的方法
    removeTodo(todoId) {
      this.todos = this.todos.filter((todo) => {
        return todo.id !== todoId;
      });
    },
    // 全选or全不选方法
    checkAllTodo(done) {
      this.todos.forEach((todo) => {
        todo.done = done;
      });
    },
  },
};
</script>

<style>
/*base*/
body {
  background: #fff;
}
.btn {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}
.btn-danger {
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}
.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}
.btn:focus {
  outline: none;
}
.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

MyHeader.vue:

<template>
  <div>
    <div class="todo-header">
      <input
        type="text"
        placeholder="请输入你的任务名称,按回车键确认"
        @keyup.enter="add"
        v-model="title"
      />
    </div>
  </div>
</template>

<script>
import { nanoid } from "nanoid";
export default {
  name: "MyHeader",
  data() {
    return {
      title: "",
    };
  },
  props: ["addTodo"],
  methods: {
    add() {
      if (this.title.trim() !== "") {
        console.log(this.title.trim);
        // 将用户输入的东西包装成为一个todo对象
        // 需要安装nanoid:npm i nanoid
        const todoObj = { id: nanoid(), title: this.title, done: false };
        this.addTodo(todoObj);
        this.title = "";
      } else {
        alert("输入不能为空");
      }
    },
  },
};
</script>

<style>
/*header*/
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.todo-header input:focus {
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
    0 0 8px rgba(82, 168, 236, 0.6);
}
</style>

MyList.vue:

<template>
  <div>
    <ul class="todo-main">
      <MyItem
        v-for="todoObj in todos"
        :key="todoObj.id"
        :todo="todoObj"
        :changeDone="changeDone"
        :removeTodo="removeTodo"
      ></MyItem>
    </ul>
  </div>
</template>

<script>
import MyItem from "./MyItem.vue";
export default {
  name: "MyList",
  components: {
    MyItem,
  },
  data() {
    return {};
  },
  props: ["todos", "changeDone", "removeTodo"],
};
</script>

<style scoped>
/*main*/
.todo-main {
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}

.todo-empty {
  height: 40px;
  line-height: 40px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding-left: 5px;
  margin-top: 10px;
}
</style>

MyItem.vue:

<template>
  <li>
    <label>
      <input
        type="checkbox"
        :checked="todo.done"
        @click="handleCheck(todo.id)"
      />
      <!--:checked和@click可以直接用v-model代替,但不建议,违反了props只读的原则 -->
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" @click="removeTodoById(todo.id)">
      删除
    </button>
  </li>
</template>

<script>
export default {
  name: "MyItem",
  props: ["todo", "changeDone", "removeTodo"],
  methods: {
    handleCheck(todoId) {
      this.changeDone(todoId);
    },
    removeTodoById(todoId) {
      if (confirm("确认删除?")) {
        // console.log(todoId);
        this.removeTodo(todoId);
      }
    },
  },
};
</script>

<style scoped>
/*item*/
li {
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
  float: left;
  cursor: pointer;
}

li label li input {
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
  content: initial;
}

li:last-child {
  border-bottom: none;
}

li:hover {
  background-color: coral;
}

li:hover button {
  display: block;
}
</style>

MyFooter.vue:

<template>
  <div>
    <div class="todo-footer" v-show="total">
      <label>
        <!-- <input type="checkbox" :checked="isAll" @click="checkAll" /> -->
        <input type="checkbox" v-model="isAll" />
      </label>
      <span>
        已完成{{ doneTotal }} / 全部{{ total }}
      </span>
      <button class="btn btn-danger" @click="removeDone">清除已完成任务</button>
    </div>
  </div>
</template>

<script>
export default {
  name: "MyFooter",
  props: ["todos", "removeTodo", "checkAllTodo"],
  computed: {
    isAll: {
      get() {
        return this.total === this.doneTotal && this.total > 0;
      },
      set(value) {
        this.checkAllTodo(value);
      },
    },
    total() {
      return this.todos.length;
    },
    doneTotal() {
      let num = 0;
      // 笨方法,遍历
      // this.todos.forEach(todo => {
      //   if(todo.done==true){
      //     num++
      //   }
      // });

      // 巧一点的方法,reduce函数
      num = this.todos.reduce((pre, todo) => {
        return pre + (todo.done ? 1 : 0);
      }, 0);

      return num;
    },
  },
  methods: {
    // 移除全部选择的todo
    removeDone() {
      this.todos.forEach((todo) => {
        if (todo.done === true) {
          this.removeTodo(todo.id);
        }
      });
    },
    checkAll(event) {
      this.checkAllTodo(event.target.checked);
    },
  },
};
</script>

<style scoped>
/*footer*/
.todo-footer {
  height: 40px;
  line-height: 40px;
  padding-left: 6px;
  margin-top: 5px;
}

.todo-footer label {
  display: inline-block;
  margin-right: 20px;
  cursor: pointer;
}

.todo-footer label input {
  position: relative;
  top: -1px;
  vertical-align: middle;
  margin-right: 5px;
}

.todo-footer button {
  float: right;
  margin-top: 5px;
}
</style>

插曲:浏览器本地存储

比如在未登陆的情况下有些搜索框会存储搜索历史记录,用到的就是浏览器本地存储。
浏览器关闭不丢失数据的是localStorage;
数据生命周期仅在一个窗口内的是sessionStorage。
在这里插入图片描述
localStorage的api

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h2>localStorage</h2>
  <button onclick="saveData()">saveData()</button>
  <button onclick="readData()">readData()</button>
  <button onclick="deleteData()">deleteData()</button>
  <script>
    function saveData(){
      let p={name:'coderhao',age:25}
      localStorage.setItem('msg','hello')
      localStorage.setItem('msg2',666)
      localStorage.setItem('person',JSON.stringify(p))
    };
    function readData(){
      console.log(localStorage.getItem('msg'));
      console.log(localStorage.getItem('msg2'));

      str=localStorage.getItem('person')
      console.log(JSON.parse(str));

      //null
      console.log(localStorage.getItem('unknow'));
    };
    function deleteData(){
      localStorage.removeItem('msg')
      // 清空
      // localStorage.clear()
    }

  </script>
</body>
</html>

sessionStorage的api和localStorage的一致,只是名字换一下:
例:
localStorage.xxxx>>>>>>>>sessionStorage.xxxx

小结:
在这里插入图片描述

对todoList案例的补充

todoList刷新会丢失数据,通过本地化存储防止数据丢失。
在这里插入图片描述

3.6 组件的自定义事件

在这里插入图片描述
App.vue:

<template>
  <div class="app">
    <!-- 方法1 传入自定义事件 -->
    <Student @getStudentName="getStudentName"></Student>
    <h2>studentName:{{ studentName }}</h2>
    <!-- 方法2 -->
    <School ref="school"></School>
    <h2>schoolName:{{ schoolName }}</h2>
  </div>
</template>

<script>
import Student from "./components/Student.vue";
import School from "./components/School.vue";
export default {
  name: "App",
  components: {
    Student,
    School,
  },
  data() {
    return {
      studentName: "",
      schoolName: "",
    };
  },
  methods: {
    getStudentName(studentName) {
      this.studentName = studentName;
    },
    getSchoolName(schoolName) {
      this.schoolName = schoolName;
    },
  },
  mounted() {
    this.$refs.school.$on("getSchoolName", this.getSchoolName);
  },
};
</script>

<style>
.app {
  background-color: crimson;
  padding: 5px;
  margin-top: 5px;
}
</style>

Student.vue:

<template>
  <div class="student">
    <h1>{{ title }}</h1>
    <h2>name:{{ name }}</h2>
    <h2>age:{{ age }}</h2>
    <button @click="sendStudentName()">this.$emit('getStudentName',this.name)</button>
  </div>
</template>

<script>
export default {
  name: "Student",
  data() {
    return {
      title: "studentInfo",
      name: "coderhao",
      age: 25,
    };
  },
  methods: {
    sendStudentName(){
      this.$emit('getStudentName',this.name)
    }
  },
};
</script>

<style>
.student {
  background-color: coral;
  padding: 5px;
  margin-top: 5px;
}
</style>

School.vue:

<template>
  <div class="school">
    <h1>{{ title }}</h1>
    <h2>name:{{ name }}</h2>
    <h2>addr:{{ addr }}</h2>
    
    <button @click="sendSchoolName()">this.$emit('getSchoolName',this.name)</button>
  </div>
</template>

<script>
export default {
  name: "School",
  data() {
    return {
      title: "schoolInfo",
      name: "atguigu",
      addr: "beijing",
    };
  },
  props:[],
  methods:{
    sendSchoolName(){
      this.$emit('getSchoolName',this.name)
    }
  }
};
</script>

<style>
.school {
  background-color: aqua;
  padding: 5px;
  margin-top: 5px;
}
</style>

自定义事件改写todoList案例

这里以MyHeader组件为例
App.vue:
在这里插入图片描述
MyHeader.vue:
在这里插入图片描述
调用事件时vue开发者工具可以看到调用细节:
在这里插入图片描述

3.7全局事件总线

在这里插入图片描述
原理:给组件ABCD通过给x绑定事件的方式,然后通过组件ABCD触发X中的事件来将数据传给相应的组件。
X得满足两个条件:首先别的组件能给X绑事件,说明所有组件得能看到X;然后X可以绑定、解绑、触发事件。

为了满足所有组件看得到,使用vc/vm做x,而且也可以绑定事件。
这里以两个兄弟组件student和school的信息传递为例:两个版本(vc和vm)
vc版本:
main.js:
在这里插入图片描述
school.vue:
在这里插入图片描述
student.vue:
在这里插入图片描述
vm版本:
main.js:
在这里插入图片描述
student.vue和school.vue同上。

一般也不叫x,叫$bus,总线:
在这里插入图片描述
在这里插入图片描述
总结:
在这里插入图片描述

全局事件总线改写todoList

在todoList的案例中,单个任务的删除方法是从App.vue–>MyList.vue–>MyItem.vue,没必要经过MyList.vue,适合使用全局事件总线。
main.js:
在这里插入图片描述
App.vue:
在这里插入图片描述
MyItem.vue:
在这里插入图片描述

3.8消息订阅与发布:pubsub

实现消息订阅与发布的有很多js库,这里使用pubsub-js实现。
安装库:

npm i pubsub-js

消息发布:
在这里插入图片描述
消息订阅:

pubsub改写todoList

App.vue:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

todoList增加编辑按钮

App.vue增加更新方法,这里用的消息总线方式
在这里插入图片描述
MyItem.vue:

<template>
  <li>
    <label>
      <input
        type="checkbox"
        :checked="todo.done"
        @click="handleCheck(todo.id)"
      />
      <span v-show="!todo.isEdit">{{ todo.title }}</span>
      <input
        v-show="todo.isEdit"
        type="text"
        :value="todo.title"
        @blur="handlerBlur(todo,$event)"
        ref="inputTitle"
      />
    </label>

    <button v-show="!todo.isEdit" @click="updateTitle(todo)">update</button>
    <button @click="deleteById(todo.id)">delete</button>
  </li>
</template>

<script>
export default {
  name: "MyItem",
  props: ["todo", "deleteTodoObj", "changeDone"],
  methods: {
    deleteById(id) {
      if (confirm("确认删除?")) {
        this.deleteTodoObj(id);
      }
    },
    handleCheck(id) {
      this.changeDone(id);
    },
    updateTitle() {
      // 添加isEdit属性
      this.$set(this.todo, "isEdit", true);
      // 下一轮执行,会在Dom节点更新完毕后执行,不然页面没渲染完,获取焦点无效
      this.$nextTick(function() {
        this.$refs.inputTitle.focus()
      })
      
    },
    // 失去焦点方法
    handlerBlur(todo,e){
      this.todo.isEdit=false
      if(!e.target.value.trim()) return alert('输入不能为空')
      // 失去焦点后更新数据
      this.$bus.$emit('updateTodo',todo.id,e.target.value)
    }
  },
};
</script>

<style>
</style>

3.9vue过渡与动画效果

3.9.1动画效果使得msg出现和消失带动画

Test.Vue:

<template>
  <div>
    <!-- 过渡标签transition包裹,可自动使用进场离场动画 -->
    <!-- v-if,v-show均可使用 -->
    <!-- :appear="true":刚出现即带动画 -->
    <transition :appear="true">
      <h2 v-show="isShow">{{msg}}</h2>
    </transition>
    <button @click="showOrNot">showOrNot</button>
  </div>
</template>

<script>
export default {
  name:'Test',
  data(){
    return{
      msg:'hello vue',
      isShow:true
    }
  },
  methods:{
    showOrNot(){
      this.isShow=!this.isShow
    }
  }
}
</script>

<style>
  h2{
    background-color: burlywood;
  }
  /* 进场动画 */
  .v-enter-active{
    animation: atguigu 1s linear;
  }
  /* 离场动画 */
  .v-leave-active{
    animation: atguigu 1s reverse;
  }

  /* transition标签如果带有name属性(<!-- <transition name="a1"> -->),则需要改变style中的对应名 */
  /* .v-enter-active》》》.a1-enter-active */
  /* 进场动画 */
  .a1-enter-active{
    /* 动画的操作名atguigu和下面的@keyframes atguigu对应 */
    animation: atguigu 1s linear;
  }
  /* 离场动画 */
  .a1-leave-active{
    animation: atguigu 1s reverse;
  }

  @keyframes atguigu{
    from{
      transform: translateX(-100%);
    }
    to{
      transform: translateX(0%);
    }
  }
</style>

3.9.2过渡效果使得msg出现消失带动画

Test2.vue:

<template>
  <div>
    <!-- 过渡标签transition包裹,可自动使用进场离场动画 -->
    <!-- v-if,v-show均可使用 -->
    <!-- :appear="true":刚出现即带动画 -->
    <transition :appear="true" name="a1">
      <h2 v-show="isShow">{{msg}}</h2>
    </transition>
    <button @click="showOrNot">showOrNot</button>
  </div>
</template>

<script>
export default {
  name:'Test2',
  data(){
    return{
      msg:'hello vue',
      isShow:true
    }
  },
  methods:{
    showOrNot(){
      this.isShow=!this.isShow
    }
  }
}
</script>

<style>
  h2{
    background-color: burlywood;
  }
 
  /* 进入的起点,离开的终点 */
  .a1-enter,.a1-leave-to{
    transform: translateX(-100%);
  }
  /* 进入,离开的过程中 */
  .a1-enter-active,.a1-leave-active{
    transition: 10s linear;
  }
  /* 进入的终点,离开的起点 */
  .a1-enter-to,.a1-leave{
    transform: translateX(0%);
  }

</style>

多元素过渡:在这里插入图片描述

3.9.3集成第三方动画

npm 安装:

npm install animate.css

Test3.vue:

<template>
  <div>
    <transition
      name="animate__animated animate__bounce"
      appear
      enter-active-class="animate__swing"
      leave-active-class="animate__backOutUp"
    >
      <h2 v-show="isShow">{{ msg }}</h2>
    </transition>

    <button @click="showOrNot">showOrNot</button>
  </div>
</template>

<script>
// 引入样式
import "animate.css";
export default {
  name: "Test3",
  data() {
    return {
      msg: "hello vue",
      isShow: true,
    };
  },
  methods: {
    showOrNot() {
      this.isShow = !this.isShow;
    },
  },
};
</script>

<style>
h2 {
  background-color: burlywood;
}
</style>

总结

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值