如何理解vue中的脚手架?
脚手架 vue-cli
用脚手架来识别vue文件
用脚手架搭建一个vue服务器项目
如何使用脚手架?
npm install -g @vue/cli
vue create ***
npm run serve
使用npm install -g @vue/cli 出现什么?
告知使用哪个版本的 vue, vue2还是vue3
使用vue2的时候,后面跟着babel和eslint是什么意思?
Babel是编译ES6----->ES5
eslint是 webpack中的代码检查,比如是否有语法错误 比如let 变量是否正确等等。
Babel和Webpack的区别:
Babel是 ES语法的转换, 转js
webpack是转html
用脚手架创建好项目之后 运行:npm run serve
出现了vue服务器,有返回的查看地址: localhost:8080
解释vue脚手架的工作流程:
工作目录:
my-project/ ├── node_modules/ ├── public/ │ ├── favicon.ico │ └── index.html ├── src/ │ ├── assets/ │ │ └── logo.png │ ├── components/ │ │ └── HelloWorld.vue │ ├── App.vue │ ├── main.js │ └── router/ │ └── index.js ├── .gitignore ├── babel.config.js ├── package.json ├── README.md └── vue.config.js
当运行npm run serve的时候,进入main.js
main.js中包含 加载App.vue和容器<div id="app"></div>
1.App.vue中是包含子组件的,如果vue中涉及的图片放在src/assets中2.容器<div id="app"></div>在public/index.html中。
容器是最终加载的vue页面中的内容。
如何理解vue中main.js中的render函数的作用?
vue文件中,引入vue的时候,不是引入vue.js而是引入一个缩减版的vue.runtime.js,这个精简版的vue.js去掉了模板解析器。为什么要去掉模板解析器呢?因为当webpack的时候已经生成了最终的html,是不需要模板解析器了,所以就用 vue.runtime.js+render的方式来代替模板解析器。
import Vue from 'vue'; import App from './App.vue'; Vue.config.productionTip = false; new Vue({ render: h => h(App), }).$mount('#app');
vue配置文件:vue.config.js
module.exports = { // 部署应用包时的基本 URL publicPath: process.env.NODE_ENV === 'production' ? '/my-app/' : '/', // 生成的生产环境构建文件的目录 outputDir: 'dist', // 放置生成的静态资源的目录 assetsDir: 'static', // 开发服务器配置 devServer: { port: 8080, // 指定开发服务器的端口号 proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, pathRewrite: { '^/api': '' } } } }, // 自定义 Webpack 配置 configureWebpack: { resolve: { alias: { '@': require('path').resolve(__dirname, 'src') } } }, // 使用 chainWebpack 修改 webpack 配置 chainWebpack: config => { config.module .rule('vue') .use('vue-loader') .loader('vue-loader') .tap(options => { // 修改 vue-loader 配置 return options }) }, // 配置 CSS 相关选项 css: { loaderOptions: { sass: { additionalData: `@import "@/styles/global.scss";` } } }, // 配置第三方插件的选项 pluginOptions: { // 配置插件选项 } };
vue2中ref的使用示例:
<template> <div id="app"> <input ref="inputElement" type="text" v-model="inputValue" /> <button @click="focusInput">Focus Input</button> <br /><br /> <ChildComponent ref="childComponent" /> <button @click="updateChildMessage">Update Child Message</button> </div> </template> <script> import ChildComponent from './components/ChildComponent.vue'; export default { components: { ChildComponent }, data() { return { inputValue: '' }; }, methods: { focusInput() { this.$refs.inputElement.focus(); }, updateChildMessage() { this.$refs.childComponent.updateMessage('Updated message from Parent Component!'); } } }; </script>
为什么需要父组件传值给子组件的场景?
为了让子组件可以作为一个公用的组件进行复用!从父组件可以传递不同的值给到子组件去使用。
父组件传值给子组件的代码示例:
父组件:
<template> <div> <ChildComponent :name="person1.name" :age="person1.age" :gender="person1.gender" /> <ChildComponent :name="person2.name" :age="person2.age" :gender="person2.gender" /> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { name: 'ParentComponent', components: { ChildComponent }, data() { return { person1: { name: '张三', age: 18, gender: '男' }, person2: { name: '李四', age: 19, gender: '女' } }; } }; </script> <style scoped> div { margin-bottom: 20px; } </style>
子组件:
<template> <div> <p>姓名: {{ name }}</p> <p>年龄: {{ age }}</p> <p>性别: {{ gender }}</p> </div> </template> <script> export default { name: 'ChildComponent', props: { name: { type: String, required: true }, age: { type: Number, required: true }, gender: { type: String, required: true } } }; </script> <style scoped> p { font-size: 16px; margin: 5px 0; } </style>
如何理解mixin的使用场景?
两个子组件,student.vue school.vue 共用一个点击事件,就不需要分别在这两个vue文件中写重复了,直接在一个公共文件中写这个方法,之后引入即可。这就是mixin
公共文件:
export const showNameMixin = { methods: { showName() { alert(`Name: ${this.name}`); } } };
student.vue
<template> <div> <p>学生姓名:{{ name }}</p> <p>学生年龄:{{ age }}</p> <button @click="showName">点我提示学生名</button> </div> </template> <script> import { showNameMixin } from '../mixins/showNameMixin'; export default { name: 'Student', mixins: [showNameMixin], data() { return { name: '张三', age: 18 }; } }; </script> <style scoped> p { font-size: 16px; margin: 5px 0; } button { margin-top: 10px; padding: 5px 10px; font-size: 14px; } </style>
school.vue
<template> <div> <p>学校名称:{{ name }}</p> <p>学校地址:{{ address }}</p> <button @click="showName">点我提示学校名</button> </div> </template> <script> import { showNameMixin } from '../mixins/showNameMixin'; export default { name: 'School', mixins: [showNameMixin], data() { return { name: '示例学校', address: '示例地址' }; } }; </script> <style scoped> p { font-size: 16px; margin: 5px 0; } button { margin-top: 10px; padding: 5px 10px; font-size: 14px; } </style>
如何使用插件?
main.js
import Vue from 'vue'; import App from './App.vue'; import examplePlugin from 'example-plugin'; // 使用插件 Vue.use(examplePlugin); new Vue({ render: h => h(App), }).$mount('#app');
vue文件:
<template> <div> <h1>{{ message }}</h1> <button @click="usePluginMethod">使用插件方法</button> </div> </template> <script> export default { data() { return { message: 'Hello, Vue!' }; }, methods: { usePluginMethod() { // 假设插件提供了一个全局方法 exampleMethod this.message = this.$exampleMethod('新消息'); } } }; </script> <style scoped> /* 样式代码 */ </style>
如何理解scoped?
定义局部样式。
举个todolist代办事项案例:
搜索框+事项+汇总信息
浏览器本地存储:localStorage和sessionStorage区别
- localStorage 适用于需要长期存储的数据,数据会在浏览器会话结束后依然存在。
- sessionStorage 适用于需要临时存储的数据,数据会在浏览器会话结束时被清除。
组件的自定义事件注意事项:
不是html标签的事件,比如 <div onlick***>
而是组件中使用事件,比如<Student v-on:自定义事件****>
组件的自定义事件示例:方法1:@事件
父组件:v-on 也可以用@代替
<!-- App.vue --> <template> <div> <h1>父组件</h1> <p>接收到的学生名字: {{ studentName }}</p> <!-- 引入并使用子组件 --> <Student @studentNameSent="handleStudentNameSent" /> </div> </template> <script> import Student from './components/Student.vue'; export default { name: 'App', components: { Student }, data() { return { studentName: '等待接收学生名字...' }; }, methods: { handleStudentNameSent(name) { this.studentName = name; } } }; </script> <style> /* 可以添加一些全局样式 */ </style>
子组件:
<!-- Student.vue --> <template> <div> <input v-model="studentName" placeholder="输入学生名字" /> <button @click="sendStudentName">发送学生名字</button> </div> </template> <script> export default { name: 'Student', data() { return { studentName: '' }; }, methods: { sendStudentName() { this.$emit('studentNameSent', this.studentName); this.studentName = ''; // 清空输入框 } } }; </script> <style scoped> /* 添加一些简单的样式 */ input { margin-right: 10px; } </style>
组件的自定义事件示例:方法2:ref (父组件on,子组件emit)
父组件:
<!-- App.vue --> <template> <div> <h1>父组件</h1> <p>接收到的学生名字: {{ studentName }}</p> <!-- 引入并使用子组件,设置 ref --> <Student ref="student" /> </div> </template> <script> import Student from './components/Student.vue'; export default { name: 'App', components: { Student }, data() { return { studentName: '等待接收学生名字...' }; }, methods: { handleStudentNameSent(name) { this.studentName = name; } }, mounted() { this.$refs.student.$on('studentNameSent', this.handleStudentNameSent); } }; </script> <style> /* 可以添加一些全局样式 */ </style>
子组件:
<!-- ChildComponent.vue --> <template> <div> <button @click="notifyParent">点击我</button> </div> </template> <script> export default { name: 'ChildComponent', methods: { notifyParent() { // 子组件的方法,不再使用 $emit return '子组件的消息'; } } }; </script>
自定义事件的解绑:使用this.$off
<button @click="unbindEvent">解绑事件</button>
this.$off('childClicked');
全局事件总线,实现兄弟之间的通信:student发消息给school
main.js
// main.js import Vue from 'vue'; import App from './App.vue'; Vue.config.productionTip = false; // 创建事件总线并将其挂载到 Vue 原型上 Vue.prototype.$bus = new Vue(); new Vue({ render: h => h(App), }).$mount('#app');
student.vue
<!-- Student.vue --> <template> <div> <input v-model="studentName" placeholder="Enter student name" /> <button @click="sendName">Send Name</button> </div> </template> <script> export default { data() { return { studentName: '' }; }, methods: { sendName() { this.$bus.$emit('studentName', this.studentName); } } }; </script>
school.vue
<!-- School.vue --> <template> <div> <h1>Received Student Name: {{ receivedName }}</h1> </div> </template> <script> export default { data() { return { receivedName: '' }; }, created() { this.$bus.$on('studentName', (name) => { this.receivedName = name; }); }, beforeDestroy() { this.$bus.$off('studentName'); } }; </script>
消息的订阅与发布:(类似 订阅on,发布emit)
下载包:
npm install pubsub-js
student.vue 发布publish
<!-- Student.vue --> <template> <div> <input v-model="studentName" placeholder="Enter student name" /> <button @click="sendName">Send Name</button> </div> </template> <script> import pubsub from 'pubsub-js'; export default { data() { return { studentName: '' }; }, methods: { sendName() { pubsub.publish('studentName', this.studentName); } } }; </script>
school.vue 订阅 subscribe
<!-- School.vue --> <template> <div> <h1>Received Student Name: {{ receivedName }}</h1> </div> </template> <script> import { onMounted, onUnmounted } from 'vue'; import pubsub from 'pubsub-js'; export default { data() { return { receivedName: '' }; }, setup() { const handleStudentName = (msg, name) => { this.receivedName = name; }; onMounted(() => { pubsub.subscribe('studentName', handleStudentName); }); onUnmounted(() => { pubsub.unsubscribe('studentName', handleStudentName); }); } }; </script>
如何理解nextTick?
在下一轮组件重构后执行。
比如当我点击一个div变成input,就执行focus。这个时候focus要等到input生成后再执行focus。如果没有nextTick一下,就无法focus,所以使用nextTick
<template> <div @click="toggleInput" v-if="!showInput">Click Me!</div> <input ref="myInput" v-model="inputValue" v-if="showInput" /> </template> <script> export default { data() { return { showInput: false, inputValue: '' }; }, methods: { toggleInput() { this.showInput = true; this.$nextTick(() => { this.$refs.myInput.focus(); }); } } }; </script>
使用animate.css 动画效果
npm install animate.css --save
<!-- MyComponent.vue --> <template> <div> <button @click="addItem">Add Item</button> <button @click="removeItem">Remove Item</button> <transition-group name="animate__animated" tag="ul"> <li v-for="item in items" :key="item" class="animate__bounce animate__fadeIn"> {{ item }} </li> </transition-group> </div> </template> <script> import 'animate.css'; export default { data() { return { items: [] }; }, methods: { addItem() { const newItem = `Item ${this.items.length + 1}`; this.items.push(newItem); }, removeItem() { this.items.pop(); } } }; </script> <style> @import 'animate.css/animate.css'; .animate__animated { animation-duration: 1s; } </style>
使用axios+proxy解决跨域问题:
安装axios:
npm install axios --save
vue.config.js (修改config文件后要重启项目)
// vue.config.js module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:5000', changeOrigin: true, pathRewrite: { '^/api': '' } } } } };
发送文件:自动识别api
<!-- MyComponent.vue --> <template> <div> <button @click="fetchData">Fetch Data</button> <div v-if="data"> <h2>Response Data:</h2> <pre>{{ data }}</pre> </div> </div> </template> <script> import axios from 'axios'; export default { data() { return { data: null, }; }, methods: { fetchData() { axios.get('/api/some-endpoint') .then(response => { this.data = response.data; }) .catch(error => { console.error('There was an error!', error); }); } } }; </script> <style> /* Add your styles here */ </style>
为什么要使用插槽?
一个App.vue 使用三个Category.vue
App.vue中传入不同的参数在Catetgory.vue中进行展示。此时Category.vue 的样式展示必须相同,否则就要使用 v-if或者v-show来代替。
比如 App.vue传入美食,category.vue中显示美食图片,App.vue中传入游戏,category.vue中显示列表,App.vue中传入电影,category.vue中显示一个视频。
这种重复的category中的判断太繁琐,所以使用插槽,就是在App.vue中传入具体的样式,并且category.vue中使用slot直接显示App.vue传输过来的样式即可。
默认插槽:
App.vue
<!-- App.vue --> <template> <div id="app"> <h1>Categories</h1> <!-- Category for Food --> <Category> <h2>Food</h2> <img src="https://via.placeholder.com/150" alt="Food" /> </Category> <!-- Category for Games --> <Category> <h2>Games</h2> <ul> <li>Game 1</li> <li>Game 2</li> <li>Game 3</li> </ul> </Category> <!-- Category for Movies --> <Category> <h2>Movies</h2> <video controls width="320"> <source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4"> Your browser does not support the video tag. </video> </Category> </div> </template> <script> import Category from './Category.vue'; export default { name: 'App', components: { Category } }; </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>
category.vue
<!-- Category.vue --> <template> <div class="category"> <slot></slot> </div> </template> <script> export default { name: 'Category' }; </script> <style scoped> .category { border: 1px solid #ccc; padding: 16px; margin-bottom: 16px; } </style>
如何理解默认插槽和具名插槽?
默认插槽是category.vue只有一个slot
具名插槽是category.vue中又多个slot,<slot****></slot>, 此时App.vue中也有对应的slot, <div slot="***">
具名插槽的示例:
App.vue
<!-- App.vue --> <template> <div id="app"> <h1>Categories</h1> <!-- Category for Food --> <Category> <template slot="title">美食</template> <template slot="content"> <img src="https://via.placeholder.com/150" alt="Food" /> </template> </Category> <!-- Category for Games --> <Category> <template slot="title">游戏</template> <template slot="content"> <ul> <li>Game 1</li> <li>Game 2</li> <li>Game 3</li> </ul> </template> </Category> <!-- Category for Movies --> <Category> <template slot="title">电影</template> <template slot="content"> <video controls width="320"> <source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4"> Your browser does not support the video tag. </video> </template> </Category> </div> </template> <script> import Category from './Category.vue'; export default { name: 'App', components: { Category } }; </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>
category.vue
<!-- Category.vue --> <template> <div class="category"> <h2><slot name="title"></slot></h2> <slot name="content"></slot> </div> </template> <script> export default { name: 'Category' }; </script> <style scoped> .category { border: 1px solid #ccc; padding: 16px; margin-bottom: 16px; } </style>
如何理解作用域插槽?
之前的默认插槽和具名插槽的作用是:在App.vue中使用样式和数据,三个不同的category三个不同的样式和数据。category.vue只需要定义<slot></slot>即可。而有一种情况是: App.vue只设置样式,而数据是放在category.vue中,并且category.vue的数据要通过<slot :aaa:"aaa">传递给App.vue,这就是作用域插槽。
为什么数据要放在category.vue?因为有时候传给cageory.vue一个id,让category.vue自己获取数据。
总结:作用域插槽就是把App.vue中的样式和数据进行分离。
作用域插槽的代码示例:
App.vue
<!-- App.vue --> <template> <div id="app"> <h1>Categories</h1> <!-- Category rendered as <li> --> <Category> <template v-slot:default="slotProps"> <ul> <li v-for="item in slotProps.items" :key="item">{{ item }}</li> </ul> </template> </Category> <!-- Category rendered as <ol> --> <Category> <template v-slot:default="slotProps"> <ol> <li v-for="item in slotProps.items" :key="item">{{ item }}</li> </ol> </template> </Category> <!-- Category rendered as <h4> --> <Category> <template v-slot:default="slotProps"> <div> <h4 v-for="item in slotProps.items" :key="item">{{ item }}</h4> </div> </template> </Category> </div> </template> <script> import Category from './Category.vue'; export default { name: 'App', components: { Category } }; </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>
cateory.vue
<!-- Category.vue --> <template> <div class="category"> <slot :items="items"></slot> </div> </template> <script> export default { name: 'Category', data() { return { items: ['Item 1', 'Item 2', 'Item 3'] }; } }; </script> <style scoped> .category { border: 1px solid #ccc; padding: 16px; margin-bottom: 16px; } </style>
如何引入vuex?
1 npm i vuex
2 Vue.use(Vuex)
3 store
4 vc==>store
main.js中引入import 文件和其他输出 的执行顺序:
import str1 from test1\
console.log(111)
console.log(222)
import str2 from test2
即使console.log在中间,输出的结果仍然是 str1 str2 111 222
因为import的文件是先执行的。
如何引入Vuex? (注意import和use的顺序)
main.js 注意只引入store.js
// src/main.js import Vue from 'vue'; import App from './App.vue'; import store from './store'; Vue.config.productionTip = false; new Vue({ store, render: h => h(App), }).$mount('#app');
store.js : 引入 Vuex
// src/store/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: { studentName: '' }, mutations: { setStudentName(state, name) { state.studentName = name; } }, actions: { updateStudentName({ commit }, name) { commit('setStudentName', name); } }, getters: { getStudentName: state => state.studentName } });
vuex中的四个配置项:这些都是写在store.js中
actions
mutations
state
getters
为什么使用 mapState,mapGetter?
由于store.js中的state ,getters
如果在组件中使用就是 {$store.state.num},但是这样写就违反简洁原则,所以在computed中写,就变成了
computed:{sum:(){ return this.$store.state.num}
*****:(){ return this.$store.state. *****}
*****:(){ return this.$store.state. *****}
}
但是 这样写 computed也很复杂,所以最终引入 mapState,mapGetter
<!-- src/components/School.vue --> <template> <div> <h2>School Component</h2> <p>Student Name: {{ studentName }}</p> </div> </template> <script> import { mapGetters } from 'vuex'; export default { computed: { ...mapGetters({ studentName: 'getStudentName' }) } }; </script> <style scoped> /* Add your styles here */ </style>
Vue路由:
main.js 引入router.js
import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, render: h => h(App), }).$mount('#app')
router.js 引入Vue-router
import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/Home' import About from '@/components/About' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About } ] })
App.vue
<template> <div id="app"> <nav> <router-link to="/">Home</router-link> <router-link to="/about">About</router-link> </nav> <router-view/> </div> </template> <script> export default { name: 'App' } </script> <style> nav { margin-bottom: 20px; } nav a { margin-right: 10px; } </style>
组件分为两种:路由组件和一般组件
路由组件放在文件夹pages/, about.vue, home.vue
一般组件放在 components中, student.vue school.vue
routes 和 route的区别:
router 是根路由
route 是路由的配置,比如 /home /about
router只有一个,route 有很多个。
当路由点击home.vue之后切换about.vue。那么之前的home.vue是隐藏还是销毁?
销毁。
如何接收路由query的参数?
App.vue (使用query对象)
<template> <div id="app"> <h1>Welcome to Vue 2 App</h1> <router-link :to="{ name: 'Detail', query: { id: '123', name: 'example' } }">Go to Detail</router-link> <router-view></router-view> </div> </template> <script> export default { name: 'App' }; </script> <style> /* 你的样式 */ </style>
Detail.vue (使用 this.$route接收)
<template> <div> <h2>Detail Page</h2> <p>Id: {{ id }}</p> <p>Name: {{ name }}</p> </div> </template> <script> export default { name: 'Detail', computed: { id() { return this.$route.query.id; }, name() { return this.$route.query.name; } } }; </script> <style> /* 你的样式 */ </style>
如何理解命名路由?
就是route.js中 给路由设置一个name名字,方便将来在route-link简略的写法调用
import Vue from 'vue'; import Router from 'vue-router'; import App from '@/App.vue'; import Detail from '@/components/Detail.vue'; Vue.use(Router); export default new Router({ routes: [ { path: '/', name: 'App', component: App }, { path: '/detail', name: 'Detail', component: Detail } ] });
route.js简略调用:
<router-link :to="{ name: 'Detail', query: { id: '123', name: 'example' } }">Go to Detail</router-link>
使用params路由:
route.js (使用 path: '/detail/:id/:name',)
import Vue from 'vue'; import Router from 'vue-router'; import App from '@/App.vue'; import Detail from '@/components/Detail.vue'; Vue.use(Router); export default new Router({ routes: [ { path: '/', name: 'App', component: App }, { path: '/detail/:id/:name', name: 'Detail', component: Detail, props: true } ] });
App.vue (使用params设置参数)
<template> <div id="app"> <h1>Welcome to Vue 2 App</h1> <router-link :to="{ name: 'Detail', params: { id: '123', name: 'example' } }">Go to Detail</router-link> <router-view></router-view> </div> </template> <script> export default { name: 'App' }; </script> <style> /* 你的样式 */ </style>
detail.vue (使用props接收)
<template> <div> <h2>Detail Page</h2> <p>Id: {{ id }}</p> <p>Name: {{ name }}</p> </div> </template> <script> export default { name: 'Detail', props: ['id', 'name'] }; </script> <style> /* 你的样式 */ </style>
如何理解编程式路由导航?
之前的路由导航都是使用<route-link >的方式,这种方式最终被编译成<a>标签。
但是有时候不需要<route-link >,比如 “后退,前进” 的button按钮,这个时候就需要
编程式路由导航。
编程式导航中的push 和replace的区别:
push式累积路由
replace 式替换最后一个路由
使用编程式导航来写“前进和后退”路由功能:
<button @click="goBack">Back</button> <button @click="goForward">Forward</button> methods: { goBack() { this.$router.go(-1); }, goForward() { this.$router.go(1); } }
如何理解keep-alive?
这个叫缓存路由组件,因为路由切换之后组件就销毁了,但是有些时候我不想销毁,就使用keep-alive。比如 一个路由中有一个输入框输入了内容,当我切换了其他路由再返回来的时候我仍然要看到之前输入框的内容,这个时候就需要路由缓存!
如何理解前置路由守卫?
当需要某个权限的角色进入某个页面,可以采用前置路由守卫的方法。
route.js (注意: const router=***, router.beforeEach)
import Vue from 'vue'; import Router from 'vue-router'; import App from '@/App.vue'; import Detail from '@/components/Detail.vue'; Vue.use(Router); const router = new Router({ routes: [ { path: '/', name: 'App', component: App }, { path: '/detail/:id/:name', name: 'Detail', component: Detail, props: true } ] }); // 前置路由守卫 router.beforeEach((to, from, next) => { if (to.name === 'Detail') { const username = localStorage.getItem('username'); if (username === 'zhangsan') { next(); // 放行 } else { next({ name: 'App' }); // 跳转到首页 } } else { next(); // 放行 } }); export default router;
如何理解独享路由守卫?
独享路由守卫的意思是专门给某个路由配置路由权限规则。
前置路由守卫的意思是:获取所有的路由规则进行判断。独享路由守卫的意思是只在某一个路由中进行配置。
route.js (注意:不用另外将const router 进行操作,直接在 routes中进行处理)
import Vue from 'vue'; import Router from 'vue-router'; import App from '@/App.vue'; import Home from '@/components/Home.vue'; import Detail from '@/components/Detail.vue'; Vue.use(Router); const router = new Router({ routes: [ { path: '/', name: 'App', component: App }, { path: '/home', name: 'Home', component: Home, beforeEnter: (to, from, next) => { const username = localStorage.getItem('username'); if (username === 'zhangsan') { next(); // 放行 } else { next({ name: 'App' }); // 跳转到首页 } } }, { path: '/detail/:id/:name', name: 'Detail', component: Detail, props: true } ] }); export default router;
如何理解 组件内的路由守卫?
全局路由守卫的前置和后置,以及独享路由守卫,都是写在route.js中。
但是组件内的路由守卫写在vue中
Detail.vue
<template> <div> <h2>Detail Page</h2> <p>Id: {{ id }}</p> <p>Name: {{ name }}</p> </div> </template> <script> export default { name: 'Detail', props: ['id', 'name'], beforeRouteEnter(to, from, next) { console.log('beforeRouteEnter: Trying to enter Detail page'); const username = localStorage.getItem('username'); if (username === 'zhangsan') { next(); } else { next({ name: 'App' }); } }, beforeRouteUpdate(to, from, next) { console.log('beforeRouteUpdate: Route is being updated'); next(); }, beforeRouteLeave(to, from, next) { console.log('beforeRouteLeave: Leaving Detail page'); next(); } }; </script> <style> /* 你的样式 */ </style>
如何理解路由的hash和history
hash: 路由中带有/#/
history:路由中 带有/
如何写history模式:
route.js ( mode: 'history', // 使用历史模式)
import Vue from 'vue'; import Router from 'vue-router'; import App from '@/App.vue'; import Home from '@/components/Home.vue'; import Detail from '@/components/Detail.vue'; Vue.use(Router); const router = new Router({ mode: 'history', // 使用历史模式 routes: [ { path: '/', name: 'App', component: App }, { path: '/home', name: 'Home', component: Home, beforeEnter: (to, from, next) => { const username = localStorage.getItem('username'); if (username === 'zhangsan') { next(); // 放行 } else { next({ name: 'App' }); // 跳转到首页 } } }, { path: '/detail/:id/:name', name: 'Detail', component: Detail, props: true } ] }); export default router;
如何打包文件(html css js)给到生产环境使用?
npm run build 生成dist文件夹 即可。
打包出来的文件可以直接打开吗?
不能直接打开, 要部署到web服务器中。比如nodejs express框架。
如何部署到 Nodejs express中?
下载express:
npm install express
express server.js
const express = require('express'); const path = require('path'); const app = express(); // 静态文件目录 app.use(express.static(path.join(__dirname, 'dist'))); // 所有路由都返回 index.html app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'dist', 'index.html')); }); // 设置端口 const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`Server is running on port ${port}`); });
将打包的文件放入。具体的目录结构: (注意此处是Vue项目中放一个NodeJs项目server,也就是将根目录的打包生成的dist文件放在了express的server中)
project-root/ ├── dist/ // Vue 项目打包生成的文件 ├── src/ // Vue 源代码文件 ├── server/ │ ├── dist/ // 复制过来的打包文件 │ ├── node_modules/ │ ├── package.json │ └── server.js // Express 服务器文件 ├── .babel.config.js ├── .package.json └── .vue.config.js
当使用路由history模式,打包成功后的dist文件夹放入nodejs中,部署成功后,浏览路由地址和页面,但是刷新页面会报错,说到服务器没有相应的路由地址,该怎么办? (目的就是服务器端也可以没有/#/ 的hash的功能的情况下刷新页面 对应的路由地址可以访问)
使用nodejs中的一个插件, connect-history-api-fallback
为了避免在使用 Vue Router 的
history
模式时刷新页面报错,可以使用connect-history-api-fallback
中间件。这个中间件会将所有的请求重定向到你的index.html
,从而确保 Vue Router 的客户端路由能够正常工作。安装插件:
npm install connect-history-api-fallback
route.js
const express = require('express'); const path = require('path'); const history = require('connect-history-api-fallback'); const app = express(); // 使用 connect-history-api-fallback 中间件 app.use(history()); // 静态文件目录 app.use(express.static(path.join(__dirname, 'dist'))); // 设置端口 const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`Server is running on port ${port}`); });
如何按需引入UI组件:
main.js中不需要全部引入:
import Vue from 'vue'; import App from './App.vue'; import router from './router'; Vue.config.productionTip = false; new Vue({ router, render: h => h(App) }).$mount('#app');
Home.vue (import { Button } from 'element-ui';)
<template> <div> <h2>Home Page</h2> <el-button type="primary" @click="handleClick">Click Me</el-button> </div> </template> <script> import { Button } from 'element-ui'; export default { name: 'Home', components: { 'el-button': Button }, methods: { handleClick() { this.$message('Button clicked!'); } } }; </script> <style> /* 你的样式 */ </style>