import Vue from 'vue'
export default new Vue()
<template>
<div>
<h1>Event Bus Sibling01</h1>
<div class="number" @click="sub">-</div>
<input type="text" style="width: 30px; text-align: center" :value="value">
<div class="number" @click="add">+</div>
</div>
</template>
<script>
import bus from './eventbus'
export default {
props: {
num: Number
},
created () {
this.value = this.num
},
data () {
return {
value: -1
}
},
methods: {
sub () {
if (this.value > 1) {
this.value--
bus.$emit('numchange', this.value)
}
},
add () {
this.value++
bus.$emit('numchange', this.value)
}
}
}
</script>
<style>
.number {
display: inline-block;
cursor: pointer;
width: 20px;
text-align: center;
}
</style>
<template>
<div>
<h1>Event Bus Sibling02</h1>
<div>{{ msg }}</div>
</div>
</template>
<script>
import bus from './eventbus'
export default {
data () {
return {
msg: ''
}
},
created () {
bus.$on('numchange', (value) => {
this.msg = `您选择了${value}件商品`
})
}
}
</script>
<style>
</style>
<template>
<div>
<h1>ref Child</h1>
<input ref="input" type="text" v-model="value">
</div>
</template>
<script>
export default {
data () {
return {
value: ''
}
},
methods: {
focus () {
this.$refs.input.focus()
}
}
}
</script>
<template>
<div>
<h1>ref Parent</h1>
<child ref="c"></child>
</div>
</template>
<script>
import child from './04-Child'
export default {
components: {
child
},
mounted () {
this.$refs.c.focus()
this.$refs.c.value = 'hello input'
}
}
</script>
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
mutations: {},
actions: {},
modules: {}
})
<template>
<div id="app">
<h1>Vuex - Demo</h1>
<!-- count:{{ count }} <br>
msg: {{ msg }} -->
<!-- count:{{ $store.state.count }} <br>
msg: {{ $store.state.msg }} -->
count: {{ num }} <br>
msg: {{ message }}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
// count: state => state.count
// ...mapState(['count', 'msg'])
...mapState({ num: 'count', message: 'msg' })
}
}
</script>
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {},
actions: {},
modules: {}
})
<template>
<div id="app">
<h1>Vuex - Demo</h1>
reverseMsg: {{ reverseMsg }}
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['reverseMsg'])
}
}
</script>
<template>
<div id="app">
<h1>Vuex - Demo</h1>
<!-- count:{{ count }} <br>
msg: {{ msg }} -->
<!-- count:{{ $store.state.count }} <br>
msg: {{ $store.state.msg }} -->
count: {{ num }} <br>
msg: {{ message }}
<h2>Getter</h2>
reverseMsg: {{ reverseMsg }}
<h2>Mutation</h2>
<!-- <button @click="$store.commit('increate', 2)">Mutation</button> -->
<button @click="increate(3)">Mutation</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
computed: {
// count: state => state.count
// ...mapState(['count', 'msg'])
...mapState({ num: 'count', message: 'msg' }),
...mapGetters(['reverseMsg'])
},
methods: {
...mapMutations(['increate'])
}
}
</script>
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {
increate (state, payload) {
state.count += payload
}
},
actions: {},
modules: {}
})
<template>
<div id="app">
<h1>Vuex - Demo</h1>
count: {{ num }} <br>
<!-- <div @click="$store.dispatch('increateAsync', 5)">Action</div> -->
<div @click="increateAsync(6)">Action</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// count: state => state.count
// ...mapState(['count', 'msg'])
...mapState({ num: 'count', message: 'msg' }),
...mapGetters(['reverseMsg'])
},
methods: {
...mapMutations(['increate']),
...mapActions(['increateAsync'])
}
}
</script>
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {
increate (state, payload) {
state.count += payload
}
},
actions: {
increateAsync (context, payload) {
setTimeout(() => {
context.commit('increate', payload)
}, 2000)
}
},
modules: {}
})
import Vue from 'vue'
import Vuex from 'vuex'
import products from './modules/products'
import cart from './modules/cart'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {
increate (state, payload) {
state.count += payload
}
},
actions: {
increateAsync (context, payload) {
setTimeout(() => {
context.commit('increate', payload)
}, 2000)
}
},
modules: {
products,
cart
}
})
const state = {
products: [
{ id: 1, title: 'iPhone 11', price: 8000 },
{ id: 2, title: 'iPhone 12', price: 10000 }
]
}
const getters = {}
const mutations = {
setProducts (state, payload) {
state.products = payload
}
}
const actions = {}
export default {
namespaced: true, // 开启命名空间
state,
getters,
mutations,
actions
}
<template>
<div id="app">
<h1>Vuex - Demo</h1>
<!-- count:{{ count }} <br>
msg: {{ msg }} -->
<!-- count:{{ $store.state.count }} <br>
msg: {{ $store.state.msg }} -->
count: {{ num }} <br>
msg: {{ message }}
<h2>Getter</h2>
reverseMsg: {{ reverseMsg }}
<h2>Mutation</h2>
<!-- <button @click="$store.commit('increate', 2)">Mutation</button> -->
<button @click="increate(3)">Mutation</button>
<!-- <div @click="$store.dispatch('increateAsync', 5)">Action</div> -->
<div @click="increateAsync(5)">Action</div>
<h2>Module</h2>
products: {{ products }} <br>
<button @click="setProducts([])">Module</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// count: state => state.count
// ...mapState(['count', 'msg'])
...mapState({ num: 'count', message: 'msg' }),
...mapGetters(['reverseMsg']),
...mapState('products', ['products'])
},
methods: {
...mapMutations(['increate', 'setProducts']),
...mapActions(['increateAsync']),
...mapMutations('products', ['setProducts'])
}
}
</script>
$store.state.msg
进行修改,从语法从面来说这是没有问题的,但是这破坏了 Vuex 的约定,如果在组件中直接修改 state,devtools 无法跟踪到这次状态的修改。export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
})
strict: process.env.NODE_ENV !== 'production',
const myPlugin = store => {
// 当store初始化后调用
store.subscribe((mutation, state) => {
// 每次mutation之后调用
// mutation的格式为{ type, payload }
})
}
import Vue from 'vue'
import Vuex from 'vuex'
import products from './modules/products'
import cart from './modules/cart'
Vue.use(Vuex)
const myPlugin = store => {
store.subscribe((mutation, state) => {
if (mutation.type.startsWith('cart/')) {
window.localStorage.setItem('cart-products', JSON.stringify(state.cart.cartProducts))
}
})
}
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
products,
cart
},
plugins: [myPlugin]
})
let _Vue = null
class Store {
constructor (options) {
const {
state = {},
getters = {},
mutations = {},
actions = {}
} = options
this.state = _Vue.observable(state)
this.getters = Object.create(null)
Object.keys(getters).forEach(key => {
Object.defineProperty(this.getters, key, {
get: () => getters[key](state)
})
})
this._mutations = mutations
this._actions = actions
}
commit (type, payload) {
this._mutations[type](this.state, payload)
}
dispatch (type, payload) {
this._actions[type](this, payload)
}
}
function install (Vue) {
_Vue = Vue
_Vue.mixin({
beforeCreate () {
if (this.$options.store) {
_Vue.prototype.$store = this.$options.store
}
}
})
}
export default {
Store,
install
}
SPA 单页应用
借鉴传统的服务器渲染
同构应用
相关概念
const express = require('express')
const fs = require('fs')
const template = require('art-template')
const app = express()
app.get('/', (req, res) => {
// 1. 获取页面模板
const templateStr = fs.readFileSync('./index.html', 'utf-8')
console.log(templateStr)
// 2. 获取数据
const data = JSON.parse(fs.readFileSync('./data.json', 'utf-8'))
console.log(data)
// 3. 渲染:数据 + 模板 = 最终结果
const html = template.render(templateStr, data)
console.log(html)
// 4. 把渲染结果发送给客户端
res.send(html)
})
app.listen(3000, () => {
console.log('running......')
})
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<p>{{message}}p>
body>
html>
{
"message": "人善被人欺,有事不要虚,所有精神小伙全部听令。整起来!天黑路滑,这社会复杂,你往高处爬,那么就有小人来找茬,精神来自灵魂,不是动手伤人,气质来自豪横,但豪横不是进牢门,懂滴撒!散会!(@阿溪)"
}
同构渲染 = 后端渲染 + 前端渲染
如何实现同构渲染?
yarn init
生成包管理器yarn add nuxt
安装Nuxt"dev": "nuxt"
<template>
<div id="app">
{{message}}
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'Home',
components: {},
// Nuxt 中提供一个钩子函数`asyncData()`,专门用于获取服务端渲染的数据。
async asyncData () {
const { data } = await axios({
method: 'GET',
// 注意此处的 URL 要指定当前端口,否则默认会去服务端的 80 端口去查找。
url: 'http://localhost:3000/data.json'
})
// 这里返回的数据会和 `data () {}` 中的数据合并到一起给页面使用
return data
}
}
</script>
<style scoped></style>
<template>
<div>
<!-- 路由出口 -->
<ul>
<li>
<!-- 类似于 router-link,用于单页面应用导航 -->
<nuxt-link to="/">Home</nuxt-link>
</li>
<li>
<!-- 类似于 router-link,用于单页面应用导航 -->
<nuxt-link to="/about">About</nuxt-link>
</li>
</ul>
<!-- 子页面出口 -->
<nuxt />
</div>
</template>
<script>
export default {
}
</script>
<style scoped></style>
客户端渲染 | 同构渲染 | |
---|---|---|
构建 | 仅构建客户端应用即可 | 需要构建两个端 |
部署 | 可以部署在任意 web 服务器中 | 只能部署在 Node.js Server 中 |
npm init -y
,并如下添加脚本{
"name": "my-app",
"scripts": {
"dev": "nuxt"
}
}
npm install --save nuxt
npm run dev
启动项目基础路由
pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'user',
path: '/user',
component: 'pages/user/index.vue'
},
{
name: 'user-one',
path: '/user/one',
component: 'pages/user/one.vue'
}
]
}
路由导航
<template>
<div>
<p>About Page</p>
<!-- a 链接,刷新导航,走服务端渲染 -->
<h2>a链接</h2>
<a href="/">首页</a>
<!-- router-link 导航连接组件 -->
<h2>router-link</h2>
<router-link to='/'>首页</router-link>
<!-- 编程式导航 -->
<button @click="onClick">首页</button>
</div>
</template>
<script>
export default {
name: "aboutPage",
methods: {
onClick() {
this.$router.push('/')
}
}
}
</script>
<style scoped>
</style>
动态路由
pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'users-id',
path: '/users/:id?',
component: 'pages/users/_id.vue'
},
{
name: 'slug',
path: '/:slug',
component: 'pages/_slug/index.vue'
},
{
name: 'slug-comments',
path: '/:slug/comments',
component: 'pages/_slug/comments.vue'
}
]
}
嵌套路由
Vue Router 嵌套路由
Nuxt.js 嵌套路由
Warning: 别忘了在父组件(.vue文件) 内增加 用于显示子视图内容
pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue
router: {
routes: [
{
path: '/users',
component: 'pages/users.vue',
children: [
{
path: '',
component: 'pages/users/index.vue',
name: 'users'
},
{
path: ':id',
component: 'pages/users/_id.vue',
name: 'users-id'
}
]
}
]
}
自定义路由配置
// Nuxt.js 配置文件
module.exports = {
router: {
// 应用的根 URL。举个例子,如果整个单页面应用的所有资源可以通过 /app/ 来访问,那么 base 配置项的值需要设置为 '/app/'
base: '/app/',
// routes: 一个数组,路由配置表
// resolve: 解析路由组件路劲
extendRoutes(routes, resolve) {
routes.push({
name: '/hello',
path: 'hello',
component: resolve(__dirname, 'pages/about.vue') // 匹配到 /hello 时加载 about.vue 组件
})
}
}
}
模板
布局
<template>
<div>
<h1>layouts/default.vue 组件</h1>
<!-- 页面出口,类似于子路由 -->
<nuxt />
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
<template>
<div>
<div>我的博客导航栏在这里</div>
<nuxt />
</div>
</template>
<template>
<!-- Your template -->
</template>
<script>
export default {
layout: 'blog'
// page component definitions
}
</script>
<template>
<div>
<h1>test1Page</h1>
<p>{{foo}}</p>
</div>
</template>
<script>
export default {
async asyncData () {
return {
// 这个数据对象在子组件中使用的话会报错
foo: 'bar'
}
}
}
</script>
<style scoped>
</style>
export default {
async asyncData (context) {
// 可以访问到 context 对象
console.log(context.params)
return {
// 这个数据对象在子组件中使用的话会报错
foo: 'bar'
}
}
}
属性字段 | 类型 | 可用 | 描述 |
---|---|---|---|
app | Vue 根实例 | 客户端 & 服务端 | 包含所有插件的 Vue 根实例。例如:在使用 axios 的时候,你想获取 a x i o s 可 以 直 接 通 过 c o n t e x t . a p p . axios 可以直接通过 context.app. axios可以直接通过context.app.axios 来获取 |
isClient | Boolean | 客户端 & 服务端 | 是否来自客户端渲染(废弃。请使用 process.client ) |
isServer | Boolean | 客户端 & 服务端 | 是否来自服务端渲染(废弃。请使用 process.server ) |
isStatic | Boolean | 客户端 & 服务端 | 是否来自 nuxt generate 静态化(预渲染)(废弃。请使用 process.static ) |
isDev | Boolean | 客户端 & 服务端 | 是否是开发 dev 模式,在生产环境的数据缓存中用到 |
isHMR | Boolean | 客户端 & 服务端 | 是否是通过模块热替换 webpack hot module replacement (仅在客户端以 dev 模式) |
route | Vue Router 路由 | 客户端 & 服务端 | Vue Router 路由实例 |
store | Vuex 数据 | 客户端 & 服务端 | Vuex.Store 实例。只有vuex 数据流存在相关配置时可用 |
env | Object | 客户端 & 服务端 | nuxt.config.js 中配置的环境变量,见 环境变量 api |
params | Object | 客户端 & 服务端 | route.params 的别名 |
query | Object | 客户端 & 服务端 | route.query 的别名 |
req | http.Request | 服务端 | Node.js API 的 Request 对象。如果 Nuxt 以中间件形式使用的话,这个对象就根据你所使用的框架而定。nuxt generate 不可用 |
res | http.Response | 服务端 | Node.js API 的 Response 对象。如果 Nuxt 以中间件形式使用的话,这个对象就根据你所使用的框架而定。nuxt generate 不可用 |
redirect | Function | 客户端 & 服务端 | 用这个方法重定向用户请求到另一个路由。状态码在服务端被使用,默认 302 redirect([status,] path [, query]) |
error | Function | 客户端 & 服务端 | 用这个方法展示错误页:error(params) 。params 参数应该包含 statusCode 和 message 字段 |
nuxtState | Object | 客户端 | Nuxt 状态,在使用 beforeNuxtRender 之前,用于客户端获取 Nuxt 状态,仅在 universal 模式下可用 |
beforeNuxtRender(fn) | Function | 服务端 | 使用此方法更新 NUXT 在客户端呈现的变量,fn 调用 (可以是异步) { Components, nuxtState } ,参考 示例 |
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/css/ionicons.min.css" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Titillium+Web:700|Source+Serif+Pro:400,700|Merriweather+Sans:400,700|Source+Sans+Pro:400,300,600,700,300italic,400italic,600italic,700italic" rel="stylesheet" type="text/css">
<!-- Import the custom Bootstrap 4 theme from our hosted CDN -->
<link rel="stylesheet" href="/index.css">
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
<template>
<div>
<!-- 顶部导航栏 -->
<nav class="navbar navbar-light">
<div class="container">
<a class="navbar-brand" href="index.html">conduit</a>
<ul class="nav navbar-nav pull-xs-right">
<li class="nav-item">
<!-- Add "active" class when you're on that page" -->
<a class="nav-link active" href="">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="">
<i class="ion-compose"></i> New Post
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="">
<i class="ion-gear-a"></i> Settings
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="">Sign up</a>
</li>
</ul>
</div>
</nav>
<!-- 顶部导航栏 -->
<!-- 子路由 -->
<nuxt-child />
<!-- 子路由 -->
<!-- 底部 -->
<footer>
<div class="container">
<a href="/" class="logo-font">conduit</a>
<span class="attribution">
An interactive learning project from <a href="https://thinkster.io">Thinkster</a>. Code & design licensed under MIT.
</span>
</div>
</footer>
<!-- 底部 -->
</div>
</template>
<script>
export default {
name: 'Header',
components: {
},
props: {
},
data() {
return {}
},
computed: {
},
watch: {
},
created() {},
mounted() {},
methods: {
},
}
</script>
<style scoped>
</style>
nuxt.config.js
文件,自定义路由module.exports = {
router: {
// 自定义路由表规则
extendRoutes(routes, resolve) {
// 清除 Nuxt.js 基于 pages 目录默认生成的路由表规则
routes.splice(0)
routes.push(...[
{
path: '/',
component: resolve(__dirname, 'pages/layouts/'),
children: [
{
path: '', // 默认子路由
name: 'home',
component: resolve(__dirname, 'pages/home/')
},
{
path: '/login',
name: 'login',
component: resolve(__dirname, 'pages/login/')
},
{
path: '/register',
name: 'register',
component: resolve(__dirname, 'pages/login/')
},
]
}
])
}
}
}
打包
最简单的部署方式
使用 PM2 启动服务
自动化部署介绍
准备自动部署内容