作者:你脑子里的铁线虫 链接:https://juejin.im/post/5e1451385188253ab54142e9
vue 咱们都知道现在用的最多的是 2.x,但是众所周知在今年的上半年,至少作者是这么说的 所以很多东西也都还没确定百分百是这样的,很有可能有改动,这个到时候再说。
本章讲的内容基本都是根据 Vue 官方 RFC来写的,因为这里面的消息还相对可信。
vue3给我的感觉有点像 vue2+mobx+ts+rxjs 的感觉,大家如果用过这些东西可能也会有这种感觉
不闲扯那些没用的,首先是生命周期 vue3舍弃了 beforeCreate 和 created,把这两个混在一起变成了一个setup
, 其实在这里面还有一个核心概念,就是vue2.x 大家用过可能都明白,就是数据响应,以数据为中心
也就是observe。这个observe的优势大家都能感觉到,就是方便,劣势就是性能,因为他这个东西本质来说就是所有的东西我都监听,其实这在大型应用来说就比较不舒服了,因为你控制不了,而且功能也比较多,消耗也就高
react大家都知道,什么都不监听,除非你用第三方的比方说 mobx
这种的,其他的都必须手动的去 setState
vue3就是在这两个方面折了一个中,这也是vue官方说的,既能够保证监听,又可以在大型应用上性能也是ok的,所以你需要自己去指定,这个要监听,那个不要监听
扯了那么多,首先咱们现在clone
一下项目
$ git clone https://github.com/vuejs/vue-next.git
$ cd vue-next && yarn
克隆下来,然后安装,这时候需要改两个东西,都在项目的根目录 分别的 rollup.config.js
加上 output.sourcemap = true
这里已经改完了,需要的朋友可以直接复制
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
也没正式发版,很多东西都不确定,配那么全也没有必要
稍微来搞一个模版,直接上代码了
Document
大概就是这种感觉,上文说过,vue
现在折了一个中,所以vue3
里推出了两个新东西(其实是一个,另一个是变种)
咱们来直接上例子 正常什么都不用的情况下,如果要是想用数据,就直接写在setup
函数返回的json
里就可以,像这样
Document
但是咱们现在这个数据,其实不是受监听的,大家可以试一下单独搞个json
,然后改变值页面是不会渲染的,这时候,就能用到咱们的reactive
了
★用法也很简单,直接调用函数里面的参数就是你的数据,返回值就是一个可监听的数据
Document
这就可以了
当然了,可能会有人感觉麻烦,感觉所有数据都得手动来监听一下,其实如果你先麻烦的化可以直接在setup
return 出来的json外直接包一个reactive
,这样你就会到了vue2
的写法,像这样
Document
而且,
reactive
自带深度监听,也就是说你这个数据不管套了多少层,json
里有多少数组,都会监听的
这个是reactive
的一个小兄弟,可能这时候有人会感觉奇怪,ref
不是获取dom
元素用的么,跟reactive
咋还能有关系呢,这个其实官方也没定下来,没确定下来到底是获取元素还是包装数据,这个可能得需要vue
发布了之后才能知道
先不说这个,先来说说ref
怎么用,首先vue3
里ref
也能装数据,只不过它是通过reactive
来实现的,背后也还是reactive
其实大家看个例子一眼就能看明白
Document
可以看到我在修改的时候改的是refData.value
,没错,其实
let refData = ref(0);
== let refData = reactive({value: 0 })
但是在模板里用的时候就不用.value
,毕竟vue
自己家的东西,至于vue
为什么要这么做,就要看最终发布是什么样子了
肯定有人会想,那我如果就是想得到ref
,比如一个input
,该怎么办呢?很简单,这里也直接上代码,然后再解释
Document
这里先是给这个ref
一个初始的一个null
,然后返回出去给到咱们的元素,这也是咱们在setup
里直接拿不到这个value
的原因,因为它还没完事,什么时候能拿到,onMounted
的时候,这个是生命周期相信大家都认识,生命周期在下文会说,这里不多赘述
对react
熟悉的兄弟这时候很有感觉对吧?
其实这个props
没有太多的变化,几句话就能说清,参数约定也还在,像这样
稍微有点区别的就是之前咱们用props数据的时候,可以直接this.props.xxx
这样获取,现在他是直接放到setup
的参数里了,像这样
当然看到这大家应该能感觉到和什么比较像了哈?
当然了,毕竟vue3
是主推ts
版的,所以当然ts
这一套也全都可以用,比如这样
当然,html
里肯定是不能直接写ts
的,这里就是说明意思
这个computed
有两种写法,一种是直接传一个函数,这个其实就相当于过去computed
只有get
,所以当你设置值的时候,是会报错的
Document
当然了,想和过去完全一样的写法当然也可以,也就是第二中写法,直接传一个json
Document
这基本上和2.x
没什么区别了,就不多解释了
★不过稍微注意一点,这个
computed
自身不带任何渲染,也就是说,如果这个refData
不是一个ref
或者说是reactive
的话,如果修改了数据,是会变没错,但是整个vue
不会去重新渲染,这也是为了灵活,如果这些东西都带的话,也就没有办法去增强性能了,所以reactive
才是vue3
整个的核心
这个用法其实跟上述的都差不多,调一下函数返回一个值,类似这样
let Version = readonly(1.1.3)
这时候可能有人会觉得有些奇怪,为什么会有这个东西,直接用const
或者ts
的readonly
不就好了么,当然了,这肯定是没错,用const
声明在这个函数里改确实会报错,但是别忘了,这个值咱们是要在setup
里返回出去的,或者传给别的地方,也就是通过vue
中转了一下,这时候就不行了,这也是有用的
注意,这个watch必须监听的是一个
reactive
的数据,如果是一个普通数据的话,用这个watch
是监听不出来的,然后咱们就来试试
还用刚才的例子来改一下
Document
也是跟过去的时候差不多,不过稍微有点区别的是,咱们有的时候可能是某一个特殊的时间要监听,过了这个特殊的时候就不监听了,这个watch
会返回一个函数,执行的话就会直接停止监听
Document
还蛮方便的对吧~
其实我相信生命周期
这东西大家还是比较关心的,比如mounted
直接写在外面就可以了,当然了,相信看到这大家应该明白vue3
的写项目的模式了,没错,也是一个函数,在setup
里调用 直接上代码了
Document
还是比较简单的,当然了,有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
写法了,不过。。。暂时吧,具体怎么样还是得看上线了之后。
- END -
推荐阅读:
1. GitHub 上能挖矿的神仙技巧 - 如何发现优秀开源项目
2. 56 道高频 JavaScript 与 ES6+ 的面试题及答案
3. 解密 BAT 大厂的初、中、高级程序员的进化之路(前端)
4. 重磅:硬核前端面试开源项目汇总(进大厂必备)