3. Vue CLI 初始化脚手架

3.1. 初始化脚手架

3.1.1. 说明

  1. Vue脚手架是Vue官方提供的标准化开发工具(开发平台)
  2. 最新的版本是 4.x
  3. 文档 Vue CLI

3.1.2. 具体步骤

  1. 如果下载缓慢请配置npm淘宝镜像 npm config set registry http://registry.npm.taobao.org
  2. 全局安装 @vue/cli npm install -g @vue/cli
  3. 切换到创建项目的目录,使用命令创建项目 vue create xxx
  4. 选择使用vue的版本
  5. 启动项目 npm run serve
  6. 打包项目 npm run build
  7. 暂停项目 Ctrl+C

Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行
vue inspect > output.js

3.1.3. 脚手架文件结构

.文件目录
├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件

脚手架Demo

components

就直接把单文件组件的 School.vue 和 Student.vue 两个文件直接拿来用,不需要修改。

School.vue

在这里插入图片描述

Student.vue

在这里插入图片描述

App.vue

引入这两个组件,注册一下这两个组件,再使用。

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <Student></Student>
    <School></School>
  </div>
</template>

<script>
import School from './components/School.vue'
import Student from './components/Student.vue'

export default {
  name: 'App',
  components: {
    School,
    Student
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

main.js:

入口文件

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

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

index.html

<!DOCTYPE html>
<html lang="">
    <head>
        <meta charset="UTF-8">
      
        <!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高渲染级别渲染页面 -->
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
      
        <!-- 开启移动端的理想端口 -->
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
      
        <!-- 配置页签图标 <%= BASE_URL %>public所在路径,使用绝对路径 -->
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
      
        <!-- 配置网页标题 -->
        <title><%= htmlWebpackPlugin.options.title %></title>
    </head>
    <body>
      
      	<!-- 当浏览器不支持js时,noscript中的元素就会被渲染 -->
      	<noscript>
      		<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    		</noscript>
          
        <!-- 容器 -->
        <div id="app"></div>
    </body>
</html>

在这里插入图片描述

3.1.4. render 函数

插入一个小知识:

使用 import 导入第三方库的时候不需要 加 ‘./’

导入我们自己写的:

import App from './App.vue'

导入第三方的

import Vue from 'vue'

不需要在 from ‘vue’ 加 ‘./’ 的原因是第三方库 node_modules 人家帮我们配置好了。

我们通过 import 导入第三方库,在第三方库的 package.json 文件中确定了我们引入的是哪个文件
在这里插入图片描述
通过 module 确定了我们要引入的文件。

备注:这是ESM默认导入的文件,CommonJS默认导入mian中路径的文件

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

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

3.1.5. 关于不同版本的函数

  1. vue.jsvue.runtime.xxx.js 的区别
    1. vue.js 是完整版的 Vue,包含:核心功能+模板解析器
    2. vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能,没有模板解析器
      esm 就是 ES6 module
  2. 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的createElement函数去指定具体内容

3.1.6. vue.config.js 配置文件

vue inspect > output.js 可以查看到Vue脚手架的默认配置
使用 vue.config.js 可以对脚手架进行个性化定制,和 package.json 同级目录,详见 配置参考 | Vue CLI

module.exports = {
  pages: {
    index: {
      entry: 'src/index/main.js' // 入口
    }
  },
  lineOnSave: false	// 关闭语法检查
}

3.2. ref 属性

ref 被用来给元素或子组件注册引用信息(id的替代者)

  • 应用在 html 标签上获取的是真实 DOM元素 ,应用在组件标签上获取的是组件实例对象vc
  • 使用方式
    • 打标识:<h1 ref="xxx"></h1><School ref="xxx"></School>
    • 获取:this.$refs.xxx
<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
    <School ref="sch"/>
  </div>
</template>

<script>
  import School from './components/School'

  export default {
    name:'App',
    components:{ School },
    data() {
      return {
        msg:'欢迎学习Vue!'
      }
    },
    methods: {
      showDOM(){
        console.log(this.$refs.title)	// 真实DOM元素
        console.log(this.$refs.btn)		// 真实DOM元素
        console.log(this.$refs.sch)		// School组件的实例对象(vc)
      }
    },
  }
</script>

在这里插入图片描述

3.3. props 配置项

props 让组件接收外部传过来的数据

  • 传递数据<Demo name="xxx" :age="18"/>这里age前加 : ,通过v-bind 使得里面的18是数字

  • 接收数据
    第一种方式(只接收)props:['name', 'age']
    第二种方式(限制类型)props:{name:String, age:Number}
    第三种方式(限制类型、限制必要性、指定默认值)

    props:{
    	name:{
            type:String, //类型
            required:true, //必要性
            default:'老王' //默认值
    	}
    }
    
    

备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

vue props传Array/Object类型值,子组件报错解决办法

在这里插入图片描述
其实看错误信息也就知道了,就是Props在传值类型为Object/Array时,如果需要配置default值(如果没有配置default值,则不会有这个报错),那必须要使用函数来return这个default值,而不能像基本数据类型那样直接写 default:xxx

错误的

//错误写法
props: {
	rlist: {
		type:Array,
		default: [1, 2, 3, 4, 5]
	}
}

正确的

//正确写法
props: {
	rlist: {
		type:Array,
		default: function() {
			return [1, 2, 3, 4, 5]
		}
	}
}
//当然,我们可以使用箭头函数来写,还显得简单很多
props: {
	rlist: {
		type:Array,
		default: () => [1, 2, 3, 4, 5]
	}
}

示例

父组件给子组件传数据

Student.vue

<template>
  <div>
    <h1>{{ msg }}</h1>
    <h2>学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <h2>学生年龄:{{ myAge + 1 }}</h2>
    <button @click="updateAge">尝试修改收到的年龄</button>
  </div>
</template>

<script>
export default {
  name: "Student",
  data() {
    console.log(this);
    return {
      msg: "我是一个bilibili大学的学生",
      myAge: this.age,
    };
  },
  methods: { updateAge() { this.myAge++; }, },
  // 简单声明接收
  // props:['name','age','sex']

  // 接收的同时对数据进行类型限制
  //   props: {
  //     name: String,
  //     age: Number,
  //     sex: String,
  //   }

  // 接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
  props: {
    name: {
      type: String, 	//name的类型是字符串
      required: true, //name是必要的
    },
    age: {
      type: Number,
      default: 99, //默认值
    },
    sex: {
      type: String,
      required: true,
    },
  },
};
</script>

App.vue

<template>
  <div>
    <Student name="李四" sex="女" :age="18"/>
    <Student name="王五" sex="男" :age="18"/>
  </div>
</template>

<script>
  import Student from './components/Student'

  export default {
    name:'App',
    components:{ Student }
  }
</script>

在这里插入图片描述

3.4. mixin 混入

  1. 功能:可以把多个组件共用的配置提取成一个混入对象
  2. 使用方式
  • 定义混入

    const mixin = {
        data() {....},
        methods: {....}
        ....
    }
    
    
  • 使用混入

    • 全局混入Vue.mixin(xxx)
    • 局部混入 mixins:['xxx']

局部混入

src/mixin.js

export const hunhe = {
	methods: {
		showName(){
			alert(this.name)
		}
	},
	mounted() {
		console.log('你好啊!')
	},
}

export const hunhe2 = {
	data() {
		return {
			x:100,
			y:200
		}
	},
}

src/components/School.vue

<template>
  <div>
    <h2 @click="showName">学校名称:{{name}}</h2>
    <h2>学校地址:{{address}}</h2>
  </div>
</template>

<script>
  //引入一个hunhe
  import {hunhe,hunhe2} from '../mixin'

  export default {
    name:'School',
    data() {
      return {
        name:'尚硅谷',
        address:'北京',
        x:666
      }
    },
    mixins:[hunhe,hunhe2]	// 局部混入
  }
</script>

src/components/Student.vue

<template>
  <div>
    <h2 @click="showName">学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
  </div>
</template>

<script>
  import {hunhe,hunhe2} from '../mixin'

  export default {
    name:'Student',
    data() {
      return {
        name:'张三',
        sex:'男'
      }
    },
    mixins:[hunhe,hunhe2]	// 局部混入
  }
</script>

全局混入

src/main.js

import Vue from 'vue'
import App from './App.vue'
import {mixin} from './mixin'

Vue.config.productionTip = false
Vue.mixin(hunhe)		// 全局混合引入
Vue.mixin(hunhe2)	// 全局混合

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

这样所有组件就自动使用这些配置了,不推荐使用全局混入

备注

  1. 组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先
var mixin = {
  data: function () {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
}

new Vue({
  mixins: [mixin],
  data: function () {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created: function () {
    console.log(this.$data)
    // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})

  1. 同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用
var mixin = {
  	created () {
    	console.log('混入对象的钩子被调用')
  	}
}

new Vue({
  	mixins: [mixin],
  	created () {
    	console.log('组件钩子被调用')
  	}
})

// => "混入对象的钩子被调用"
// => "组件钩子被调用"

3.5. plugin 插件

  1. 功能:用于增强 Vue
  2. 本质:包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据
  3. 定义插件(见下 src/plugin.js)
  4. 使用插件:Vue.use()

Vue.use执行之后,会自动调用 install 方法,所有组件就可以使用里面定义的东西。

src/plugin.js

export default {
  install(Vue,x,y,z){
    console.log(x,y,z)
      
    //定义全局指令
    Vue.directive('fbind',{
      //指令与元素成功绑定时(一上来)
      bind(element,binding){element.value = binding.value},
      //指令所在元素被插入页面时
      inserted(element,binding){element.focus()},
      //指令所在的模板被重新解析时
      update(element,binding){element.value = binding.value}
    })

    //定义混入
    Vue.mixin({
      data() {return {x:100,y:200}},
    })

    //给Vue原型上添加一个方法(vm和vc就都能用了)
    Vue.prototype.hello = ()=>{alert('你好啊')}
  }
}

src/main.js

import Vue from 'vue'
import App from './App.vue'
import plugins from './plugins'	// 引入插件

Vue.config.productionTip = false

Vue.use(plugins,1,2,3)	// 应用(使用)插件

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

src/components/School.vue

<template>
  <div>
    <h2>学校名称:{{ name | mySlice }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <button @click="test">点我测试一个hello方法</button>
  </div>
</template>

<script>
  export default {
    name:'School',
    data() {
      return {
        name:'尚硅谷atguigu',
        address:'北京',
      }
    },
    methods: {
      test(){
        this.hello()
      }
    },
  }
</script>

src/components/Student.vue

<template>
  <div>
    <h2>学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <input type="text" v-fbind:value="name">
  </div>
</template>

<script>
  export default {
    name:'Student',
    data() {
      return {
        name:'张三',
        sex:'男'
      }
    },
  }
</script>

在这里插入图片描述

3.6. scoped样式

  1. 作用:让样式在局部生效,防止冲突
  2. 写法:<style scoped>

Vue中的webpack并没有安装最新版,导致有些插件也不能默认安装最新版,如 npm i less-loader@7,而不是最新版

src/components/School.vue

<template>
  <div class="demo">
    <h2 class="title">学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
  </div>
</template>

<script>
  export default {
    name:'School',
    data() {
      return {
        name:'尚硅谷atguigu',
        address:'北京',
      }
    }
  }
</script>

<style scoped>
  .demo{
    background-color: skyblue;
  }
</style>

src/components/Student.vue

<template>
  <div class="demo">
    <h2 class="title">学生姓名:{{ name }}</h2>
    <h2 class="atguigu">学生性别:{{ sex }}</h2>
  </div>
</template>

<script>
  export default {
    name: 'Student',
    data() {
      return {
        name: '张三',
        sex: '男'
      }
    }
  }
</script>

<style lang="less" scoped>
  .demo {
    background-color: pink;
    .atguigu {
      font-size: 40px;
    }
  }
</style>

src/App.vue

<template>
  <div>
    <h1 class="title">你好啊</h1>
    <School/>
    <Student/>
  </div>
</template>

<script>
  import Student from './components/Student'
  import School from './components/School'

  export default {
    name: 'App',
    components: { School, Student }
  }
</script>

<style scoped>
  .title {
    color: red;
  }
</style>

在这里插入图片描述

3.7. Todo-List 案例

组件化编码流程

  1. 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突

  2. 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用

    • 一个组件在用:放在组件自身即可
    • 一些组件在用:放在他们共同的父组件上(状态提升)
  3. 实现交互:从绑定事件开始

props适用于

  1. 父组件 ==> 子组件 通信
  2. 子组件 ==> 父组件 通信(要求父组件先给子组件一个函数)

使用 v-model 时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做

src/App.vue
<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader :addTodo="addTodo"/>
        <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
        <MyFooter :todos="todos" 
                  :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
    	</div>
    </div>
  </div>
</template>

<script>
  import MyHeader from './components/MyHeader'
  import MyList from './components/MyList'
  import MyFooter from './components/MyFooter.vue'

  export default {
    name: 'App',
    components: { MyHeader, MyList, MyFooter },
    data() {
      return {
        // 由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
        todos:[
          {id:'001',title:'抽烟',done:true},
          {id:'002',title:'喝酒',done:false},
          {id:'003',title:'开车',done:true}
        ]
      }
    },
    methods: {
      //添加一个todo
      addTodo(todoObj){
        this.todos.unshift(todoObj)
      },
      //勾选or取消勾选一个todo
      checkTodo(id){
        this.todos.forEach((todo)=>{
          if(todo.id === id) todo.done = !todo.done
        })
      },
      //删除一个todo
      deleteTodo(id){
        this.todos = this.todos.filter( todo => todo.id !== id )
      },
      //全选or取消全选
      checkAllTodo(done){
        this.todos.forEach((todo)=>{
          todo.done = done
        })
      },
      //清除所有已经完成的todo
      clearAllTodo(){
        this.todos = this.todos.filter((todo)=>{
          return !todo.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>

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

<script>
	import {nanoid} from 'nanoid'
	export default {
		name:'MyHeader',
		props:['addTodo'],	// 接收从App传递过来的addTodo
		data() {
			return {
				title:''				// 收集用户输入的title
			}
		},
		methods: {
			add(){
				// 校验数据
				if(!this.title.trim()) return alert('输入不能为空')
				// 将用户的输入包装成一个todo对象
				const todoObj = { id:nanoid(), title:this.title, done:false }
				// 通知App组件去添加一个todo对象
				this.addTodo(todoObj)
				// 清空输入
				this.title = ''
			}
		},
	}
</script>

<style scoped>
	/*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>

src/components/MyList.vue

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

<script>
  import MyItem from './MyItem'

  export default {
    name:'MyList',
    components:{MyItem},
    // 声明接收App传递的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的
    props:['todos','checkTodo','deleteTodo']
  }
</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>
src/components/MyItem.vue
<template>
  <li>
    <label>
      <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
      <!-- <input type="checkbox" v-model="todo.done"/> -->
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>  
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  </li>
</template>

<script>
  export default {
    name:'MyItem',
    //声明接收todo、checkTodo、deleteTodo
    props:['todo','checkTodo','deleteTodo'],
    methods: {
      // 勾选or取消勾选
      handleCheck(id){
        this.checkTodo(id)	// 通知App组件将对应的todo对象的done值取反
      },
      // 删除
      handleDelete(id){
        if(confirm('确定删除吗?')){
          this.deleteTodo(id)	// 通知App组件将对应的todo对象删除
        }
      }
    },
  }
</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: #ddd;}
  li:hover button{display: block;}
</style>
src/components/MyFooter.vue
<template>
  <div class="todo-footer" v-show="total">
    <label>
      <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
      <input type="checkbox" v-model="isAll"/>
    </label>
    <span>
      <span>已完成{{ doneTotal }}</span> / 全部{{ total }}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
  export default {
    name:'MyFooter',
    props:['todos','checkAllTodo','clearAllTodo'],
    computed: {
      // 总数
      total(){
        return this.todos.length
      },
      // 已完成数
      doneTotal(){
        //此处使用reduce方法做条件统计
        return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
      },
      // 控制全选框
      isAll:{
        //全选框是否勾选
        get(){
          return this.doneTotal === this.total && this.total > 0
        },
        //isAll被修改时set被调用
        set(value){
          this.checkAllTodo(value)
        }
      }
    },
    methods: {
      /* checkAll(e){
				this.checkAllTodo(e.target.checked)
			} */
      //清空所有已完成
      clearAll(){
        this.clearAllTodo()
      }
    },
  }
</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>

在这里插入图片描述

3.8. WebStorage(js 本地存储)

Cookie

Cookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用 于控制 Cookie 有效期、安全性、使用范围的可选属性组成。不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。

Cookie的特性:

  • Cookie一旦创建成功,名称就无法修改
  • Cookie是无法跨域名的,也就是说a域名和b域名下的cookie是无法共享的,这也是由Cookie的隐私安全性决定的,这样就能够阻止非法获取其他网站的Cookie
  • 每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb
  • 有安全问题,如果Cookie被拦截了,那就可获得session的所有信息,即使加密也于事无补,无需知道cookie的意义,只要转发cookie就能达到目的
  • Cookie在请求一个新的页面的时候都会被发送过去

Cookie 在身份认证中的作用

客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动 将 Cookie 保存在浏览器中。

随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给 服务器,服务器即可验明客户端的身份。
在这里插入图片描述

Cookie 不具有安全性

由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全 性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。

注意:千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等。

Session

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了session是一种特殊的cookie。cookie是保存在客户端的,而session是保存在服务端。

为什么要用session
由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了

session原理
当客户端第一次请求服务器的时候,服务器生成一份session保存在服务端,将该数据(session)的id以cookie的形式传递给客户端;以后的每次请求,浏览器都会自动的携带cookie来访问服务器(session数据id)。

为什么要用session
由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了

session原理
当客户端第一次请求服务器的时候,服务器生成一份session保存在服务端,将该数据(session)的id以cookie的形式传递给客户端;以后的每次请求,浏览器都会自动的携带cookie来访问服务器(session数据id)。

在这里插入图片描述

session我觉得可以简单理解为一个表,根据cookie传来的值查询表中的内容

session 标准工作流程

在这里插入图片描述

Storage

存储内容大小一般支持 5MB 左右(不同浏览器可能还不一样)
浏览器端通过 Window.sessionStorageWindow.localStorage 属性来实现本地存储机制

相关API

xxxStorage.setItem('key', 'value')该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
xxxStorage.getItem('key')该方法接受一个键名作为参数,返回键名对应的值
xxxStorage.removeItem('key')该方法接受一个键名作为参数,并把该键名从存储中删除
xxxStorage.clear()该方法会清空存储中的所有数据
备注

  • SessionStorage存储的内容会随着浏览器窗口关闭而消失
  • LocalStorage存储的内容,需要手动清除才会消失
  • xxxStorage.getItem(xxx)如果 xxx 对应的 value 获取不到,那么getItem()的返回值是null
  • JSON.parse(null)的结果依然是null
localStorage

LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。

LocalStorage的优点:

  • 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息
  • LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在
  • 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带

LocalStorage的缺点:

  • 存在浏览器兼容问题,IE8以下版本的浏览器不支持
  • 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage
  • LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问
// 保存数据到 localStorage
localStorage.setItem('key', 'value');

// 从 localStorage 获取数据
let data = localStorage.getItem('key');

// 从 localStorage 删除保存的数据
localStorage.removeItem('key');

// 从 localStorage 删除所有保存的数据
localStorage.clear();

// 获取某个索引的Key
localStorage.key(index)

LocalStorage的使用场景:

有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可
在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中

SessionStorage

SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。

SessionStorageLocalStorage 对比:

  • SessionStorage和LocalStorage都在本地进行数据存储
  • SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage 只有在同一浏览器的同一窗口下才能够共享
  • LocalStorage和SessionStorage都不能被爬虫爬取
// 保存数据到 sessionStorage
sessionStorage.setItem('key', 'value');

// 从 sessionStorage 获取数据
let data = sessionStorage.getItem('key');

// 从 sessionStorage 删除保存的数据
sessionStorage.removeItem('key');

// 从 sessionStorage 删除所有保存的数据
sessionStorage.clear();

// 获取某个索引的Key
sessionStorage.key(index)

SessionStorage的使用场景

由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。

案例

localStorage

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>localStorage</title>
	</head>
	<body>
		<h2>localStorage</h2>
		<button onclick="saveData()">点我保存一个数据</button>
		<button onclick="readData()">点我读取一个数据</button>
		<button onclick="deleteData()">点我删除一个数据</button>
		<button onclick="deleteAllData()">点我清空一个数据</button>

		<script type="text/javascript" >
			let p = {name:'张三',age:18}

			function saveData(){
				localStorage.setItem('msg','hello!!!')
				localStorage.setItem('msg2',666)
                // 转成 JSON 对象存进去
				localStorage.setItem('person',JSON.stringify(p))
			}
			function readData(){
				console.log(localStorage.getItem('msg'))
				console.log(localStorage.getItem('msg2'))

				const result = localStorage.getItem('person')
				console.log(JSON.parse(result))

				// console.log(localStorage.getItem('msg3'))
			}
			function deleteData(){
				localStorage.removeItem('msg2')
			}
			function deleteAllData(){
				localStorage.clear()
			}
		</script>
	</body>
</html>

sessionStorage

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>sessionStorage</title>
	</head>
	<body>
		<h2>sessionStorage</h2>
		<button onclick="saveData()">点我保存一个数据</button>
		<button onclick="readData()">点我读取一个数据</button>
		<button onclick="deleteData()">点我删除一个数据</button>
		<button onclick="deleteAllData()">点我清空一个数据</button>

		<script type="text/javascript" >
			let p = {name:'张三',age:18}

			function saveData(){
				sessionStorage.setItem('msg','hello!!!')
				sessionStorage.setItem('msg2',666)
                // 转换成JSON 字符串存进去
				sessionStorage.setItem('person',JSON.stringify(p))
			}
			function readData(){
				console.log(sessionStorage.getItem('msg'))
				console.log(sessionStorage.getItem('msg2'))

				const result = sessionStorage.getItem('person')
				console.log(JSON.parse(result))

				// console.log(sessionStorage.getItem('msg3'))
			}
			function deleteData(){
				sessionStorage.removeItem('msg2')
			}
			function deleteAllData(){
				sessionStorage.clear()
			}
		</script>
	</body>
</html>

3.9. 组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件
  2. 使用场景:子组件想给父组件传数据,那么就要在父组件中给子组件绑定自定义事件(事件的回调在A中)
  3. 绑定自定义事件
    1. 第一种方式,在父组件中<Demo @事件名="方法"/><Demo v-on:事件名="方法"/>
    2. 第二种方式,在父组件中this.$refs.demo.$on('事件名',方法)
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
<Demo ref="demo"/>
......
mounted(){
   this.$refs.demo.$on('atguigu',this.test)
}
  1. 触发自定义事件 this.$emit('事件名',数据)
  2. 解绑自定义事件 this.$off('事件名')
  3. 组件上也可以绑定原生DOM事件,需要使用native修饰符 @click.native="show"
    上面绑定自定义事件,即使绑定的是原生事件也会被认为是自定义的,需要加native,加了后就将此事件给组件的根元素
  4. 注意:通过this.$refs.xxx.$on('事件名',回调函数)绑定自定义事件时,回调函数要么配置在methods中,要么用箭头函数,否则 this 指向会出问题

第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

App.vue

<template>
	<div class="app">
		<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
		<Student @atguigu="getStudentName"/> 
	</div>
</template>

<script>
	import Student from './components/Student'

	export default {
		name:'App',
		components:{Student},
		data() {
			return {
				msg:'你好啊!',
				studentName:''
			}
		},
		methods: {
			getStudentName(name,...params){
				console.log('App收到了学生名:',name,params)
				this.studentName = name
			}
		}
	}
</script>

<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>


src/components/Student.vue

<template>
	<div class="student">
		<button @click="sendStudentlName">把学生名给App</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
			}
		},
		methods: {
			sendStudentlName(){
				//触发Student组件实例身上的atguigu事件
				this.$emit('atguigu',this.name,666,888,900)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

第二种方式,在父组件中:

使用 this.$refs.xxx.$on() 这样写起来更灵活,比如可以加定时器啥的。

App.vue

<template>
	<div class="app">
		<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
		<Student ref="student"/>
	</div>
</template>

<script>
	import Student from './components/Student'

	export default {
		name:'App',
		components:{Student},
		data() {
			return {
				studentName:''
			}
		},
		methods: {
			getStudentName(name,...params){
				console.log('App收到了学生名:',name,params)
				this.studentName = name
			},
		},
		mounted() {
			this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
			// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)
		},
	}
</script>

<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

Student.vue

<template>
	<div class="student">
		<button @click="sendStudentlName">把学生名给App</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
			}
		},
		methods: {
			sendStudentlName(){
				//触发Student组件实例身上的atguigu事件
				this.$emit('atguigu',this.name,666,888,900)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。
触发自定义事件:this.$emit('atguigu',数据)
使用 this.$emit() 就可以子组件向父组件传数据

解绑自定义事件this.$off('atguigu')

this.$off('atguigu') //解绑一个自定义事件
// this.$off(['atguigu','demo']) //解绑多个自定义事件
// this.$off() //解绑所有的自定义事件

组件上也可以绑定原生DOM事件,需要使用 native 修饰符。

3.10. 全局事件总线(GlobalEventBus)

一种可以在任意组件间通信的方式,本质上就是一个对象,它必须满足以下条件

  1. 所有的组件对象都必须能看见他
  2. 这个对象必须能够使用 $on $emit $off方法去绑定、触发和解绑事件

使用步骤
3. 定义全局事件总线

new Vue({
	......
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
	},
    ......
}) 
  1. 使用事件总线
    1. 接收数据:A组件想接收数据,则在A组件中给 $bus 绑定自定义事件,事件的回调留在A组件自身
methods(){
  demo(data){......}
}
......
mounted() {
  this.$bus.$on('xxxx',this.demo)
}

提供数据:this.$bus.$emit('xxx',data)

  1. 最好在beforeDestroy钩子中,用$off()去解绑当前组件所用到的事件

因为销毁的时候只会把组件销毁,不会把$bus上的销毁,下车要把东西带走。

示例代码

src/main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  el:'#app',
  render: h => h(App),
  beforeCreate() {
    Vue.prototype.$bus = this // 安装全局事件总线
  }
})

src/App.vue

<template>
	<div class="app">
		<School/>
		<Student/>
	</div>
</template>

<script>
	import Student from './components/Student'
	import School from './components/School'

	export default {
		name:'App',
		components:{ School, Student }
	}
</script>

<style scoped>.app{background-color: gray;padding: 5px;}</style>

School.vue

<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
        methods: {
            hello(data) {
                console.log('我是School组件,收到了数据',data)
            }
        }
		mounted() {
			// console.log('School',this)
			this.$bus.$on('hello',this.demo)
		},
		beforeDestroy() {
			this.$bus.$off('hello')
		},
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

Student.vue

<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<button @click="sendStudentName">把学生名给School组件</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男',
			}
		},
		mounted() {
			// console.log('Student',this.x)
		},
		methods: {
			sendStudentName(){
				this.$bus.$emit('hello',this.name)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

在这里插入图片描述

3.11. 消息的订阅与发布(基本不用)

消息订阅与发布(pubsub)消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信
使用步骤

  1. 安装pubsub:npm i pubsub-js
  2. 引入:import pubsub from 'pubsub-js'
  3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
export default {
    methods: {
        demo(msgName, data) {...}
    }
    ...
    mounted() {
			this.pid = pubsub.subscribe('xxx',this.demo)
    }
}
  1. 提供数据:pubsub.publish('xxx',data)
  2. 最好在beforeDestroy钩子中,使用 pubsub.unsubscribe(pid)取消订阅

示例代码

src/components/School.vue

<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	import pubsub from 'pubsub-js'

	export default {
		name: 'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		methods: {
			demo(msgName, data) {
				console.log('我是School组件,收到了数据:',msgName, data)
			}
		},
		mounted() {
			this.pubId = pubsub.subscribe('demo', this.demo) // 订阅消息
		},
		beforeDestroy() {
			pubsub.unsubscribe(this.pubId) // 取消订阅
		}
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

src/components/Student.vue

<template>
  <div class="student">
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <button @click="sendStudentName">把学生名给School组件</button>
  </div>
</template>

<script>
  import pubsub from 'pubsub-js'

  export default {
    name:'Student',
    data() {
      return {
        name:'JOJO',
        sex:'男',
      }
    },
    methods: {
      sendStudentName(){
        pubsub.publish('demo', this.name) // 发布消息
      }
    }
  }
</script>

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

3.12. $nextTick

语法:this.$nextTick(回调函数)
作用:在下一次 DOM 更新结束后执行其指定的回调。
什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

this.$nextTick(function({
	this.$refs.inputTitle.focus()
}

3.13. 过渡与动画

Vue 封装的过度与动画:在插入、更新或移除 DOM 元素时,在合适的时候给元素添加样式类名

在这里插入图片描述
写法

  1. 准备好样式
    • 元素进入的样式
      • v-enter 进入的起点
      • v-enter-active 进入过程中
      • v-enter-to 进入的终点
    • 元素离开的样式
      • v-leave 离开的起点
      • v-leave-active 离开过程中
      • v-leave-to 离开的终点
  2. 使用 <transition> 包裹要过度的元素,并配置 name 属性,此时需要将上面样式名的 v 换为name
  3. 要让页面一开始就显示动画,需要添加 appear

具体案例(单个元素过渡)

<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<transition appear>
			<h1 v-show="isShow">你好啊!</h1>
		</transition>
	</div>
</template>

<script>
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}

	.v-enter-active{
		animation: move 0.5s linear;
	}

	.v-leave-active{
		animation: move 0.5s linear reverse;
	}

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

  1. 备注:若有多个元素需要过度,则需要使用<transition-group>,且每个元素都要指定key

name 的作用可以让让不同的元素有不同的动画效果


<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<transition name="hello" appear>
			<h1 v-show="isShow">你好啊!</h1>
		</transition>
	</div>
</template>

<script>
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}

	.hello-enter-active{
		animation: move 0.5s linear;
	}

	.hello-leave-active{
		animation: move 0.5s linear reverse;
	}

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

  1. 第三方动画库Animate.css
库的名称:Animate.css
安装:npm i animate.css
引入:import ‘animate.css’
<transition-group appear
          name="animate__animated animate__bounce"
          enter-active-class="animate__swing"
          leave-active-class="animate__backOutUp">
  <h1 v-show="!isShow" key="1">你好啊!</h1>
  <h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>

src/App.vue

<template>
	<div>
		<Test/>
		<Test2/>
		<Test3/>
	</div>
</template>

<script>
	import Test from './components/Test'
	import Test2 from './components/Test2'
	import Test3 from './components/Test3'

	export default {
		name:'App',
		components:{Test,Test2,Test3},
	}
</script>

src/components/test.vue

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition name="hello" appear>
      <h1 v-show="isShow">你好啊!</h1>
    </transition>
  </div>
</template>

<script>
  export default {
    name: 'Test',
    data() {return {isShow:true}},
  }
</script>

<style scoped>
  h1{background-color: orange;}

  .hello-enter-active{
    animation: atguigu 0.5s linear;
  }

  .hello-leave-active{
    animation: atguigu 0.5s linear reverse;
  }

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

src/components/test2

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition-group name="hello" appear>
      <h1 v-show="!isShow" key="1">你好啊!</h1>
      <h1 v-show="isShow" key="2">尚硅谷!</h1>
    </transition-group>
  </div>
</template>

<script>
  export default {
    name:'Test',
    data() {return {isShow:true}},
  }
</script>

<style scoped>
  h1 {
    background-color: orange;
    /* transition: 0.5s linear; */
  }
  /* 进入的起点、离开的终点 */
  .hello-enter,.hello-leave-to {
    transform: translateX(-100%);
  }
  .hello-enter-active,.hello-leave-active{
    transition: 0.5s linear;
  }
  /* 进入的终点、离开的起点 */
  .hello-enter-to,.hello-leave {
    transform: translateX(0);
  }
</style>

src/components/test3

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition-group appear
                      name="animate__animated animate__bounce"
                      enter-active-class="animate__swing"
                      leave-active-class="animate__backOutUp">
      <h1 v-show="!isShow" key="1">你好啊!</h1>
      <h1 v-show="isShow" key="2">尚硅谷!</h1>
    </transition-group>
  </div>
</template>

<script>
  import "animate.css"
  
  export default {
    name: "Test",
    data() {return {isShow: true,}},
  };
</script>

<style scoped>
  h1 {background-color: orange;}
</style>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值