Vue3体验卡~
2020/1/23 11:07:19
本文主要是介绍Vue3体验卡~,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言
vue咱们都知道现在用的最多的是2.x,但是众所周知在今年的上半年,至少作者是这么说的 所以很多东西也都还没确定百分百是这样的,很有可能有改动,这个到时候再说
本章讲的内容基本都是根据Vue 官方 RFC来写的,因为这里面的消息还相对可信
与vue 2.x的区别以及个人观感
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
也没正式发版,很多东西都不确定,配那么全也没有必要
稍微来搞一个模版,直接上代码了
<!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
里推出了两个新东西(其实是一个,另一个是变种)
reactive - 监听数据
咱们来直接上例子
正常什么都不用的情况下,如果要是想用数据,就直接写在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
里有多少数组,都会监听的
ref
这个是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
没有太多的变化,几句话就能说清,参数约定也还在,像这样
稍微有点区别的就是之前咱们用props数据的时候,可以直接this.props.xxx
这样获取,现在他是直接放到setup
的参数里了,像这样
当然了,毕竟vue3
是主推ts
版的,所以当然ts
这一套也全都可以用,比如这样
当然,html
里肯定是不能直接写ts
的,这里就是说明意思
computed
这个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
整个的核心
readonly
这个用法其实跟上述的都差不多,调一下函数返回一个值,类似这样
let Version = readonly(1.1.3)
这时候可能有人会觉得有些奇怪,为什么会有这个东西,直接用const
或者ts
的readonly
不就好了么,当然了,这肯定是没错,用const
声明在这个函数里改确实会报错,但是别忘了,这个值咱们是要在setup
里返回出去的,或者传给别的地方,也就是通过vue
中转了一下,这时候就不行了,这也是有用的
watch
注意,这个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 复制代码
微信
Thank You这篇关于Vue3体验卡~的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04package.json 文件位置在哪?-icode9专业技术文章分享
- 2024-10-01Craco.js学习:从入门到实践指南
- 2024-10-01Create-React-App学习:入门与实践指南
- 2024-10-01CSS-in-JS学习:从入门到实践指南
- 2024-09-30JSX语法学习:从入门到初步掌握
- 2024-09-30Mock.js学习:入门教程与实战演练
- 2024-09-30React Hooks学习:从入门到实践
- 2024-09-30受控组件学习:React中的基础入门教程
- 2024-09-29JS定时器教程:初学者必看指南
- 2024-09-29JS对象教程:初学者的全面指南