因爲vue更新後與文中demo不兼容 ,再此說明一下, 本文的全部內容是基於3.0.0-alpha.1
版本編寫,各位小夥伴在學習的時候注意下版本哦javascript
vue我們都知道如今用的最多的是2.x,可是衆所周知在今年的上半年,至少做者是這麼說的 因此不少東西也都還沒肯定百分百是這樣的,頗有可能有改動,這個到時候再說html
本章講的內容基本都是根據Vue 官方 RFC來寫的,由於這裏面的消息還相對可信vue
vue3給個人感受有點像 vue2+mobx+ts+rxjs 的感受,你們若是用過這些東西可能也會有這種感受java
不閒扯那些沒用的,首先是生命週期 vue3捨棄了 beforeCreate 和 created,把這兩個混在一塊兒變成了一個setup
, 其實在這裏面還有一個核心概念,就是vue2.x 你們用過可能都明白,就是數據響應,以數據爲中心node
也就是observe。這個observe的優點你們都能感受到,就是方便,劣勢就是性能,由於他這個東西本質來講就是全部的東西我都監聽,其實這在大型應用來講就比較不舒服了,由於你控制不了,並且功能也比較多,消耗也就高react
react你們都知道,什麼都不監聽,除非你用第三方的比方說 mobx
這種的,其餘的都必須手動的去 setState
git
vue3就是在這兩個方面折了一箇中,這也是vue官方說的,既可以保證監聽,又能夠在大型應用上性能也是ok的,因此你須要本身去指定,這個要監聽,那個不要監聽github
扯了那麼多,首先我們如今clone
一下項目vuex
$ git clone https://github.com/vuejs/vue-next.git
$ cd vue-next && yarn
複製代碼
克隆下來,而後安裝,這時候須要改兩個東西,都在項目的根目錄 分別的 rollup.config.js
typescript
加上 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
裏推出了兩個新東西(實際上是一個,另外一個是變種)
我們來直接上例子 正常什麼都不用的狀況下,若是要是想用數據,就直接寫在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></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></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.cn/post/684490…
有什麼問題歡迎在評論提問,或者加我qq或者微信,一塊兒溝通
916829411
複製代碼
微信
Thank You