vue我们都知道如今用的最多的是2.x,可是众所周知在今年的上半年,至少做者是这么说的 因此不少东西也都还没肯定百分百是这样的,颇有可能有改动,这个到时候再说javascript
本章讲的内容基本都是根据Vue 官方 RFC来写的,由于这里面的消息还相对可信html
vue3给个人感受有点像 vue2+mobx+ts+rxjs 的感受,你们若是用过这些东西可能也会有这种感受vue
不闲扯那些没用的,首先是生命周期 vue3舍弃了 beforeCreate 和 created,把这两个混在一块儿变成了一个setup
, 其实在这里面还有一个核心概念,就是vue2.x 你们用过可能都明白,就是数据响应,以数据为中心java
也就是observe。这个observe的优点你们都能感受到,就是方便,劣势就是性能,由于他这个东西本质来讲就是全部的东西我都监听,其实这在大型应用来讲就比较不舒服了,由于你控制不了,并且功能也比较多,消耗也就高node
react你们都知道,什么都不监听,除非你用第三方的比方说 mobx
这种的,其余的都必须手动的去 setState
react
vue3就是在这两个方面折了一个中,这也是vue官方说的,既可以保证监听,又能够在大型应用上性能也是ok的,因此你须要本身去指定,这个要监听,那个不要监听git
扯了那么多,首先我们如今clone
一下项目github
$ git clone https://github.com/vuejs/vue-next.git
$ cd vue-next && yarn
复制代码
克隆下来,而后安装,这时候须要改两个东西,都在项目的根目录 分别的 rollup.config.js
vuex
加上 output.sourcemap = true
这里已经改完了,须要的朋友能够直接复制typescript
import fs from 'fs'
import path from 'path'
import ts from 'rollup-plugin-typescript2'
import replace from '@rollup/plugin-replace'
import json from '@rollup/plugin-json'
if (!process.env.TARGET) {
throw new Error('TARGET package must be specified via --environment flag.')
}
const masterVersion = require('./package.json').version
const packagesDir = path.resolve(__dirname, 'packages')
const packageDir = path.resolve(packagesDir, process.env.TARGET)
const name = path.basename(packageDir)
const resolve = p => path.resolve(packageDir, p)
const pkg = require(resolve(`package.json`))
const packageOptions = pkg.buildOptions || {}
const knownExternals = fs.readdirSync(packagesDir).filter(p => {
return p !== '@vue/shared'
})
// ensure TS checks only once for each build
let hasTSChecked = false
const outputConfigs = {
'esm-bundler': {
file: resolve(`dist/${name}.esm-bundler.js`),
format: `es`
},
// main "vue" package only
'esm-bundler-runtime': {
file: resolve(`dist/${name}.runtime.esm-bundler.js`),
format: `es`
},
cjs: {
file: resolve(`dist/${name}.cjs.js`),
format: `cjs`
},
global: {
file: resolve(`dist/${name}.global.js`),
format: `iife`
},
esm: {
file: resolve(`dist/${name}.esm.js`),
format: `es`
}
}
const defaultFormats = ['esm-bundler', 'cjs']
const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(',')
const packageFormats = inlineFormats || packageOptions.formats || defaultFormats
const packageConfigs = process.env.PROD_ONLY
? []
: packageFormats.map(format => createConfig(format, outputConfigs[format]))
if (process.env.NODE_ENV === 'production') {
packageFormats.forEach(format => {
if (format === 'cjs' && packageOptions.prod !== false) {
packageConfigs.push(createProductionConfig(format))
}
if (format === 'global' || format === 'esm') {
packageConfigs.push(createMinifiedConfig(format))
}
})
}
export default packageConfigs
function createConfig(format, output, plugins = []) {
if (!output) {
console.log(require('chalk').yellow(`invalid format: "${format}"`))
process.exit(1)
}
output.externalLiveBindings = false
output.sourcemap = true
const isProductionBuild =
process.env.__DEV__ === 'false' || /\.prod\.js$/.test(output.file)
const isGlobalBuild = format === 'global'
const isRawESMBuild = format === 'esm'
const isBundlerESMBuild = /esm-bundler/.test(format)
const isRuntimeCompileBuild = /vue\./.test(output.file)
if (isGlobalBuild) {
output.name = packageOptions.name
}
const shouldEmitDeclarations =
process.env.TYPES != null &&
process.env.NODE_ENV === 'production' &&
!hasTSChecked
const tsPlugin = ts({
check: process.env.NODE_ENV === 'production' && !hasTSChecked,
tsconfig: path.resolve(__dirname, 'tsconfig.json'),
cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'),
tsconfigOverride: {
compilerOptions: {
declaration: shouldEmitDeclarations,
declarationMap: shouldEmitDeclarations
},
exclude: ['**/__tests__', 'test-dts']
}
})
// we only need to check TS and generate declarations once for each build.
// it also seems to run into weird issues when checking multiple times
// during a single build.
hasTSChecked = true
const entryFile =
format === 'esm-bundler-runtime' ? `src/runtime.ts` : `src/index.ts`
return {
input: resolve(entryFile),
// Global and Browser ESM builds inlines everything so that they can be
// used alone.
external:
isGlobalBuild || isRawESMBuild
? []
: knownExternals.concat(Object.keys(pkg.dependencies || [])),
plugins: [
json({
namedExports: false
}),
tsPlugin,
createReplacePlugin(
isProductionBuild,
isBundlerESMBuild,
(isGlobalBuild || isRawESMBuild || isBundlerESMBuild) &&
!packageOptions.enableNonBrowserBranches,
isRuntimeCompileBuild
),
...plugins
],
output,
onwarn: (msg, warn) => {
if (!/Circular/.test(msg)) {
warn(msg)
}
}
}
}
function createReplacePlugin( isProduction, isBundlerESMBuild, isBrowserBuild, isRuntimeCompileBuild ) {
const replacements = {
__COMMIT__: `"${process.env.COMMIT}"`,
__VERSION__: `"${masterVersion}"`,
__DEV__: isBundlerESMBuild
? // preserve to be handled by bundlers
`(process.env.NODE_ENV !== 'production')`
: // hard coded dev/prod builds
!isProduction,
// this is only used during tests
__TEST__: isBundlerESMBuild ? `(process.env.NODE_ENV === 'test')` : false,
// If the build is expected to run directly in the browser (global / esm builds)
__BROWSER__: isBrowserBuild,
// is targeting bundlers?
__BUNDLER__: isBundlerESMBuild,
// support compile in browser?
__RUNTIME_COMPILE__: isRuntimeCompileBuild,
// support options?
// the lean build drops options related code with buildOptions.lean: true
__FEATURE_OPTIONS__: !packageOptions.lean && !process.env.LEAN,
__FEATURE_SUSPENSE__: true
}
// allow inline overrides like
//__RUNTIME_COMPILE__=true yarn build runtime-core
Object.keys(replacements).forEach(key => {
if (key in process.env) {
replacements[key] = process.env[key]
}
})
return replace(replacements)
}
function createProductionConfig(format) {
return createConfig(format, {
file: resolve(`dist/${name}.${format}.prod.js`),
format: outputConfigs[format].format
})
}
function createMinifiedConfig(format) {
const { terser } = require('rollup-plugin-terser')
return createConfig(
format,
{
file: resolve(`dist/${name}.${format}.prod.js`),
format: outputConfigs[format].format
},
[
terser({
module: /^esm/.test(format)
})
]
)
}
复制代码
还有就是 tsconfig.json
,须要吧sourceMap
改为true
,这里也直接上代码了
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
"sourceMap": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"allowJs": false,
"noUnusedLocals": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitThis": true,
"experimentalDecorators": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"removeComments": false,
"jsx": "preserve",
"lib": ["esnext", "dom"],
"types": ["jest", "puppeteer", "node"],
"rootDir": ".",
"paths": {
"@vue/*": ["packages/*/src"]
}
},
"include": [
"packages/global.d.ts",
"packages/runtime-dom/jsx.d.ts",
"packages/*/src",
"packages/*/__tests__",
"test-dts"
]
}
复制代码
而后随便建一个目录,我这里叫public
, 像这样
而后执行 yarn dev
或者 cnpm run dev
而后在html
启动引入vue.js
path : ../packages/vue/dist/vue.global.js
上文提到 vue
的 beforeCreate
和 created
都换成了 setup
,那我们就来试一下,固然了,本篇的例子直接食用cdn方式来实验的,毕竟如今vue
也没正式发版,不少东西都不肯定,配那么全也没有必要
稍微来搞一个模版,直接上代码了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> var App = { template: ` <div class="container"> </div>`, setup() { } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
大概就是这种感受,上文说过,vue
如今折了一个中,因此vue3
里推出了两个新东西(实际上是一个,另外一个是变种)
我们来直接上例子 正常什么都不用的状况下,若是要是想用数据,就直接写在setup
函数返回的json
里就能够,像这样
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive } = Vue var App = { template: ` <div class="container"> {{aaa}} </div>`, setup() { return { aaa: 123 } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
可是我们如今这个数据,其实不是受监听的,你们能够试一下单独搞个json
,而后改变值页面是不会渲染的,这时候,就能用到我们的reactive
了
用法也很简单,直接调用函数里面的参数就是你的数据,返回值就是一个可监听的数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive } = Vue var App = { template: ` <div class="container"> 名字 {{data.name}} <br/> 年龄 {{data.age}} <br/> </div>`, setup() { let data = reactive({name: 'name', age: 18}) return { data } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
这就能够了
固然了,可能会有人感受麻烦,感受全部数据都得手动来监听一下,其实若是你先麻烦的化能够直接在setup
return 出来的json外直接包一个reactive
,这样你就会到了vue2
的写法,像这样
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive } = Vue var App = { template: ` <div class="container"> 名字 {{data.name}} <br/> 年龄 {{data.age}} <br/> </div>`, setup() { let data = {name: 'name', age: 18} return reactive({ data }) } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
并且,
reactive
自带深度监听,也就是说你这个数据无论套了多少层,json
里有多少数组,都会监听的
这个是reactive
的一个小兄弟,可能这时候有人会感受奇怪,ref
不是获取dom
元素用的么,跟reactive
咋还能有关系呢,这个其实官方也没定下来,没肯定下来究竟是获取元素仍是包装数据,这个可能得须要vue
发布了以后才能知道
先不说这个,先来讲说ref
怎么用,首先vue3
里ref
也能装数据,只不过它是经过reactive
来实现的,背后也仍是reactive
其实你们看个例子一眼就能看明白
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive, ref } = Vue var App = { template: ` <div class="container"> {{refData}} <button @click="handleClick()">按钮</button> </div>`, setup() { let refData = ref(0); const handleClick = () =>{ refData.value+=1 } console.log(refData) return { refData, handleClick } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
能够看到我在修改的时候改的是refData.value
,没错,其实
let refData = ref(0);
== let refData = reactive({value: 0 })
可是在模板里用的时候就不用.value
,毕竟vue
本身家的东西,至于vue
为何要这么作,就要看最终发布是什么样子了
确定有人会想,那我若是就是想获得ref
,好比一个input
,该怎么办呢? 很简单,这里也直接上代码,而后再解释
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive, ref, watch, onMounted } = Vue var App = { template: ` <div class="container"> <input ref="input1" value="321" /> </div>`, setup() { const input1 = ref(null); console.log(input1.value) onMounted(()=>{ console.log(input1.value.value) }) return { input1 } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
这里先是给这个ref
一个初始的一个null
,而后返回出去给到我们的元素,这也是我们在setup
里直接拿不到这个value
的缘由,由于它还没完事,何时能拿到,onMounted
的时候,这个是生命周期相信你们都认识,生命周期在下文会说,这里很少赘述
对react
熟悉的兄弟这时候颇有感受对吧?
其实这个props
没有太多的变化,几句话就能说清,参数约定也还在,像这样
稍微有点区别的就是以前我们用props数据的时候,能够直接this.props.xxx
这样获取,如今他是直接放到setup
的参数里了,像这样
固然了,毕竟vue3
是主推ts
版的,因此固然ts
这一套也全均可以用,好比这样
固然,html
里确定是不能直接写ts
的,这里就是说明意思
这个computed
有两种写法,一种是直接传一个函数,这个其实就至关于过去computed
只有get
,因此当你设置值的时候,是会报错的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive, ref, computed } = Vue var App = { template: ` <div class="container"> {{count}} <button @click="handleClick()">按钮</button> </div>`, setup() { let refData = ref(0); let count = computed(()=>{ return refData.value; }) const handleClick = () =>{ count.value+=1 } console.log(refData) return { count, handleClick } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
固然了,想和过去彻底同样的写法固然也能够,也就是第二中写法,直接传一个json
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive, ref, computed } = Vue var App = { template: ` <div class="container"> {{count}} <button @click="handleClick()">按钮</button> </div>`, setup() { let refData = ref(0); let count = computed({ get(){ return refData.value; }, set(value){ console.log(value) refData.value = value; } }) const handleClick = () =>{ count.value+=1 } console.log(refData) return { count, handleClick } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
这基本上和2.x
没什么区别了,就很少解释了
不过稍微注意一点,这个
computed
自身不带任何渲染,也就是说,若是这个refData
不是一个ref
或者说是reactive
的话,若是修改了数据,是会变没错,可是整个vue
不会去从新渲染,这也是为了灵活,若是这些东西都带的话,也就没有办法去加强性能了,因此reactive
才是vue3
整个的核心
这个用法其实跟上述的都差很少,调一下函数返回一个值,相似这样
let Version = readonly(1.1.3)
这时候可能有人会以为有些奇怪,为何会有这个东西,直接用const
或者ts
的readonly
不就行了么,固然了,这确定是没错,用const
声明在这个函数里改确实会报错,可是别忘了,这个值我们是要在setup
里返回出去的,或者传给别的地方,也就是经过vue
中转了一下,这时候就不行了,这也是有用的
注意,这个watch必须监听的是一个
reactive
的数据,若是是一个普通数据的话,用这个watch
是监听不出来的,而后我们就来试试
还用刚才的例子来改一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive, ref, watch } = Vue var App = { template: ` <div class="container"> {{refData}} <button @click="handleClick()">按钮</button> </div>`, setup() { let refData = ref(0); const handleClick = () =>{ refData.value+=1 } watch(refData,(val, oldVal)=>{ console.log(val, oldVal) }) return { refData, handleClick } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
也是跟过去的时候差很少,不过稍微有点区别的是,我们有的时候多是某一个特殊的时间要监听,过了这个特殊的时候就不监听了,这个watch
会返回一个函数,执行的话就会直接中止监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive, ref, watch } = Vue var App = { template: ` <div class="container"> {{refData}} <button @click="handleClick()">按钮</button> <button @click="handleStop">中止</button> </div>`, setup() { let refData = ref(0); const handleClick = () =>{ refData.value+=1 } let stop = watch(refData,(val, oldVal)=>{ console.log(val, oldVal) }) const handleStop = () =>{ stop() } return { refData, handleClick, handleStop } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
还蛮方便的对吧~
其实我相信生命周期
这东西你们仍是比较关心的,好比mounted
直接写在外面就能够了,固然了,相信看到这你们应该明白vue3
的写项目的模式了,没错,也是一个函数,在setup
里调用 直接上代码了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../packages/vue/dist/vue.global.js"></script>
<div id="app"></div>
<script> const { reactive, ref, watch, onMounted } = Vue var App = { template: ` <div class="container"> {{refData}} <button @click="handleClick()">按钮</button> <button @click="handleStop">中止</button> </div>`, setup() { onMounted(()=>{ console.log('mounted~'); }) let refData = ref(0); const handleClick = () =>{ refData.value+=1 } let stop = watch(refData,(val, oldVal)=>{ console.log(val, oldVal) }) const handleStop = () =>{ stop() } return { refData, handleClick, handleStop } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
复制代码
仍是比较简单的,固然了,有onMounted
就有on
别的,这都同样的,除了那个卸载的更名了,叫onUnmounted
在vue2.x
里我们组件间共享数据是否是只能props
,稍微复杂点的共享就得用vuex
,可是这个东西一多,就太乱了
因此vue3
里提供了另外两个东西
provide - 提供数据
inject - 获得数据
这里我就直接写伪代码了
const userData = Symbol();
const Cmp1 = {
setup(){
let user = reactive({name: "aaa", age: 18});
provide(userData, user);
}
}
const Cmp2 = {
setup(){
const user = inject(userData, {});
}
}
复制代码
这个就很简单了,你们应该能看明白,不过为何要用Symbol
呢,你们知道Symbol
出来的东西永远都是惟一的,这也是官方推荐的写法,若是要是你纯自定义,很容易重名或者打错字母什么之类的,因此我们只要保管好这个key
,数据共享就会变的很方便
这个inject第二个参数就是默认值,也就是说没有这个数据的时候默认值是什么,这个仍是比较好理解的
这个多说一句,其实我我的是比较喜欢的,其实你们能够仔细想一想,以前在写vue
的时候,不少程度的时间都在搞父子组件,什么兄弟组件,这个那个的互相传,很费劲,有了这个,就能够彻底抛弃以前的写法了,只须要想办法怎么把这个数据统一管理、声明也好,省了不少麻烦事
虽然说2.x
里也有,不过官方也说了,推荐在高级组件里或者库里用,也不推荐你写,毕竟还有不少没完善
其实看到这你们应该都感受到了,vue3
这回仍是很是注重这个setup
函数的,其实这样也有很大的好处,等于说vue
此次把不少的决定权交给你了,再也不是全部的东西都是它背后作的了,也很方便
固然了,可能会有人以为全部的东西全放到一个地方太乱了,其实也还好,由于你能够本身去作模块化什么之类的,这个看你本身的管理了,不过我预感可能官方还会出一个什么之类的规范
不过如今的小道消息也不可信,具体都还须要等具体vue3
发版才能知道
可能有朋友比较喜欢vue 2.x
的装饰器写法,这里官方说明了,装饰器如今毕竟还不稳定,框架上也有一下烂七八糟的不方便,this
什么的,对继承也没什么帮助,因此vue3
就直接舍弃class
写法了,不过。。。暂时吧,具体怎么样仍是得看上线了以后
最后,安利一个vue3
的文章,也是我一个朋友写的,你们若是对vue3
还有兴趣,能够去看看 juejin.im/post/5e13ec…
有什么问题欢迎在评论提问,或者加我qq或者微信,一块儿沟通
916829411
复制代码
微信
Dyy916829411
复制代码
Thank You