Based on using
The following code has been tested, you can copy directly to see the effect. Note the introduction of Vue files
All codes will be uploaded to GitHub or the code cloud after all updates are completed. I will update it as soon as possible
The Vue base section and the handwritten Vue section (the explanation has not been updated yet) have been uploaded. Click here to do so
Partial updates for VueX and VueRouter have also been uploaded. You need to click here
VueX has an auxiliary function and so on. The VueRouter is almost complete. Unit testing and SSR may follow
Aachen’s official account
Render priority
render
>template
>data
Interpolation expression of- When {{}} is an expression, it outputs the result and internally converts it to a function
<html lang="en">
<head>
<meta charset="UTF-8">
<title>The basic concept</title>
<script src="vue.js"></script>
</head>
<body>
<h1>Display priority</h1>
<ul>
<li>The first one is render. The render method outputs the render result</li>
<li>The second is the template method</li>
<li>The last one is data, and if neither exists, the interpolation in data is printed</li>{{}}} when an expression is placed inside, the result of the output expression is converted to a function called render</ul>
<p>Directive modifiers, there are a lot of their own official website to see</p>
<div id="app">
{{ msg }}
</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
msg: 'I am data',}},template: '< div > I am a template < / div >',
render(h) {
return h('div'['I am a render'])},method: {
fn(e) { // The event source is automatically added without parentheses, and the event source is passed in manually with parentheses
console.log('We already bound this internally with bind, there's no point in binding it again')
console.log(this)}}})</script>
</body>
</html>
Copy the code
v-model
V-model is actually a syntactic sugar
<input type="text" :value = 'msg' @input="handleInput">
<! -- actually the grammar sugar above -->
<input type="text" v-model="msg">
Copy the code
V – the application of the model
<html lang="en">
<head>
<meta charset="UTF-8">
<title>v-model</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
{{ msg }}
<p>@input</p>
<input type="text" :value = 'msg' @input="handleInput">
<p>This is @ chage</p>
<input type="text" :value = 'msg' @change="handleInput">
<p>V-model is the syntactic sugar of @input above</p>
<input type="text" v-model="msg">
<p>The difference between @input and @change is when you focus and when you lose focus</p>
<p>The drop-down list</p>
{{ selected }}<br>
<select v-model="selected">
<option value="" disabled>Please select a</option>
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
</select>
<p>The value bound in this way must be a list</p>
{{ selectedMore }}<br>
<select v-model="selectedMore" multiple>
<option value="" disabled>Please select a</option>
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
</select>
<p>Check box</p>
{{ checked }}<br>swimming<input v-model="checked" type="checkbox" value="Swimming">Take a shower<input v-model="checked" type="checkbox" value="Shower">Go to bed<input v-model="checked" type="checkbox" value="Sleep">
<p>Radio buttons</p>
{{ radioed }}<br>
男 <input type="radio" value="Male" v-model="radioed">
女 <input type="radio" value="Female" v-model="radioed">
<p>V - model modifier</p>
<p>{{ attr }}</p>
<input type="number" v-model.number="attr">
<p>{{attrText}} works like @chage</p>
<input type="text" v-model.lazy="attrText">
<p>{{attrText}} removes whitespace</p>
<input type="text" v-model.trim="attrText">
</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
msg: 'I am data'.selected:' '.selectedMore: [].checked: [].radioed:' '.attr:0.attrText:' '}},methods: {
handleInput(e){
this.msg = e.target.value
}
},
})
</script>
</body>
</html>
Copy the code
watch
The corresponding function is performed for changes in observed values
Three ways to write:
- add
deep
Property indicating deep traversal - add
immediate
Property indicating immediate execution - add
name
Property, executemethods
This method of
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="Width =device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
{{ msg }}
{{ name }}
</div>
<script src="vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
msg: { a: '123' },
name:'456'}},methods: {
fn() {
console.log('this is the methods')}},/ / the first
// watch:{
// msg:{
// handler(oldValue,newValue){
// console.log(oldValue,newValue) // if the object is less than the oldValue
/ /},
// deep: true // If the object continues deep traversal
/ /}
// }
watch: {
msg: [
/ / {
// handler(oldValue, newValue) {
// console.log(oldValue, newValue) // if the object is less than the oldValue
/ /},
// immediate: true, // Executes immediately
// },
// 'fn', // why not].name:'fn'
},
})
setTimeout((a)= > {
vm.msg.a = '456'
vm.name = '789'
}, 1000)
</script>
</body>
</html>
Copy the code
computed
Get is often used, but there is also a set
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="Width =device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">Selection:<input type="checkbox" v-model="checkAll">
<hr>
<input type="checkbox" v-for="check in checks" v-model="check.check">
</div>
<script src="vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
checks: [{ check: true }, { check: true }, { check: true}]}},computed: {
checkAll: {
get() {
// Return false if one does not satisfy and do not proceed
return this.checks.every((item) = > item.check)
},
set(newValue) {
this.checks.forEach(item= > item.check = newValue)
},
},
},
})
</script>
</body>
</html>
Copy the code
Watch and computed
- Computed doesn’t take value right away, it takes value when it’s needed. And there is a cache, depending on the data does not change does not update the results
- Watch executes immediately, calculating an old value first. The function is executed when the data changes
filter
Filter, which formats the attributes before displaying them
It is divided into global and local
It takes two arguments, one for the data to be formatted and one for the formatting rule
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/[email protected]/dayjs.min.js"></script>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
<p>local</p>
{{ timer | format1('YYYY:MM:DD') }}
<p>global</p>
{{ timer | format('YYYY:MM:DD') }}
</div>
<script>
Vue.filter('format'.function (timer, format) {
return dayjs(timer).format(format)
})
let vm = new Vue({
el:'#app',
data() {
return {
timer: 123456789,}},filters:{
format1(timer, format){
return dayjs(timer).format(format)
}
}
})
</script>
</body>
</html>
Copy the code
instruction
Also divided into local and global
To use it, add v-xxx XXX as the instruction name on the label you want to use
<html lang="en">
<head>
<meta charset="UTF-8">
<title>instruction</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
<p>Automatic focus capture</p>
<input type="text" v-focus>
<p>Click to show calendar effect</p>
<div v-click-outside="hide">
<input type="text" @focus="show">
<div v-if="isShow">Calendar display time</div>
</div>
<h1>Instructions have a life cycle. Have a hook</h1>
<ul>
<li>Bind is executed once on the binding</li>
<li>[INSERT</li>
<li>Update When reference data changes</li>
<li>ComponentUpdate Updates templates</li>
<li>Unbind Unbind</li>
<li>The default is written as a function bind+update</li>
</ul>
<h1>The directive passes in the meaning of the three arguments</h1>
<ul>
<li>El Current element</li>
<li>Each attribute of Bindings relevant instruction</li>
<li>VNode Virtual node</li>
<li>VNode. Context Vue instance</li>
</ul>
</div>
<script>
// Global register directive
Vue.directive('focus', {
// When the bound element is inserted into the DOM...
inserted: function (el) {
// Focus elements
el.focus()
},
})
let vm = new Vue({
el: '#app'.// Local directives
directives: {
clickOutside: {
bind(el, bindings, vNode) {
el.handler = function (e) {
// console.log(e.target)
console.log(vNode.context)
if(! el.contains(e.target)) { vNode.context[bindings.expression]() } }document.addEventListener('click', el.handler)
},
unbind(el) {
document.removeEventListener('click', el.handler)
},
},
},
data() {
return {
isShow: false,}},methods: {
show() {
this.isShow = true
},
hide() {
this.isShow = false}},})</script>
</body>
</html>
Copy the code
Instance attributes
This section describes some common instance properties
$mount()
Mount, parameter write node to be mounted. If not written, it is mounted$el
Property, you can manually mount it (for example, write Message pop-up)$options
Gets the user write configuration$watch
It’s the same as watch
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Instance attributes</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
{{ msg }}
</div>
<script>
let vm = new Vue({
// el:'#app',
data() {
return {
msg: 'I am data',}},template: '< div > I am a template < / div >',
render(h) {
return h('div'['I am a render'])
},
})
vm.$mount() // Mount the value to the corresponding node
// Mount to $el without providing it means manually mount it
console.log(vm.$el) // Get the real node
document.body.appendChild(vm.$el)
console.log(vm.$options) // User parameters
console.log(vm.$watch('msg'.function (oldValue, newValue) {
console.log(oldValue, newValue)
})) // This is just another way of saying watch
</script>
</body>
</html>
Copy the code
The advanced
animation
Animation is divided into two kinds, one is CSS animation, one is JS animation. You choose according to your needs
Since I personally recommend using CSS for animation, THE JS version will not be written. Those who are interested can click here
CSS version
You wrap the DOM element that you want to animate with transition
Then memorize the six names that correspond to different periods of the animation
.v-enter
When I go into the animation.v-enter-active
Enter the animation process.v-enter-to
Enter the end of the animation.v-leave
It doesn’t make any sense. It’s aesthetic.v-leave-active
Leave the animation process.v-leave-to
End of animation
<html lang="en">
<head>
<meta charset="UTF-8">
<title>animation</title>
<script src=".. /vue.js"></script>
</head>
<body>
<div id="app">
<p>Transiton can have a name attribute to change the name, so some V-leave becomes name-leave</p>
<transition>
<div v-show="isShow" class="box" style=" width: 100px; height: 100px;">
</div>
</transition>
<button @click="handleShow">Am I</button>
<p>Transition Vue Animation label transition-group Animation group</p>
</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
isShow: false,}},methods: {
handleShow() {
this.isShow = !this.isShow
},
},
})
</script>
<style>
.box {
background-color: red
}
/* Enter the animation color */
.v-enter {
background-color: blue;
}
/* During animation */
.v-enter-active {
transition: all 2s linear;
}
/* End of the animation */
.v-enter-to {
background-color: yellow;
}
/* will change to red */
/* This has no practical meaning, for aesthetics */
.v-leave {
background-color: purple;
}
.v-leave-active{
transition: all 2s linear;
}
.v-leave-to{
background-color: blue;
}
</style>
</body>
</html>
Copy the code
Animation group
Unlike the previous one, this number is multiple groups of animation.
Transition-group is used differently
The name of the animation
enter-class
enter-active-class
enter-to-class
(2.1.8 +)leave-class
leave-active-class
leave-to-class
(2.1.8 +)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>animation</title>
<script src=".. /vue.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app">
<p>Vue animation group</p>
<input type="text" v-model="content">
<transition-group
enter-active-class="animated bounceInLeft"
leave-active-class="animated bounceOutRight"
>
<li v-for="arr in computedArr" :key="arr">{{ arr }}</li>
</transition-group>
</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
content:' '.arrs: ['abc'.'basd'.'zxcw'.'awqec'.'kjea']}},methods: {
handleShow() {
this.isShow = !this.isShow
},
},
computed:{
computedArr(){
return this.arrs.filter(item= > item.includes(this.content))
}
}
})
</script>
<style>
li{
width: 200px;
background-color: blue;
line-height: 35px;
}
</style>
</body>
</html>
Copy the code
component
Component Communication (emphasis)
I have summarized the following
props
+emit
provide
+inject
Single data flow$parent
+$children
Triggers an event for a parent or child directly$broadcast
+$dispatch
I wrote it on a prototype$attrs
+$listeners
From a collection of all properties and methods$bus
similarVuex
Vuex
Vue
The plug-in
props+emit
// parents <template> <div> <h1>Parent</h1> <h2> first kind </h2> <Son :money="money" :changMoney="changMoney"></Son> <p> second method Click2 is a self-defined name, not a native event </p> <Son :money="money" @click2="changMoney"></Son> </div> </template> <script> import Son from './Son' export default { name: 'Parent', data() { return { money: 200, } }, components: { Son }, methods: { changMoney(value) { this.money = value }, changMoney2(val) { this.money += val }, }, } </script> // son <template> <div> <h1>Son</h1> <p> After receiving this, the son </h1> <p> child component can use the properties and functions passed by the parent component </p> {{money}} <h2> first kind </h2> <button @click="changMoney(500)"> change father money </button> </h2> second kind of method </h2> </button </button> </div> </template> <script> export default {props:{money: { type:Number, default:100 }, changMoney:{ type:Function, default: ()=>{} } }, methods:{ change(){ this.$emit('click2',300) } } } </script>Copy the code
The first is to pass in a property and a function that the child can use again after receiving it
The second way is to use $emit to directly fire functions defined at the parent level
Note that click2 is not native, you can call it a, B, etc
provide
+inject
Official advice:
Provide and Inject are mainly used when developing high-level plug-in/component libraries. Not recommended for use in normal application code.
This one is easier. It’s like Redux for React
// parent <template> <div> <h1> parent </h1> <h1> About son2 cross generation communication </h1> < son2@eat ="eat"></ son2 ></ div> </template> <script> import Son2 from './Son2' export default { provide(){ return {parent:this} }, name: 'Parent', data() { return { money: 200, } }, components: { Son2, }, methods: {eat(){console.log(' eat method in patent ')}}, } </script> // son2 <template> <div> Son2 <GrandSon></GrandSon> </div> </template> <script> import GrandSon from './GrandSon' export default { name:'Son2', components: { GrandSon, }, } </script> // grandSon <template> <div> grandSon <p> </p> {{parent-money}} <button @click="$parent.$emit('eat')" </button> </div> </template> <script> export default {inject:['parent']} </script>Copy the code
So the whole point of writing a Son2 is just to show you that you can do this. One is provided and one is received and then used
$parent
+$children
I’m just going to use the code above. This is the easy one. $parent/$children = $parent/$children Then use or trigger their properties or methods
$broadcast
+ $dispatch
To quote the official word again
$dispatch and $broadcast have been deprecated. Use more concise communication between components and better state management solutions such as Vuex.
Of course, we still introduce some of these two methods, you need to use (whisper bb, I think Vuex really smells good)
/ / in the main. Js
import Vue from 'vue'
import App from './App';
/** * Find the parent node to trigger the event * @param eventName * @param ComName * @param Value */
Vue.prototype.$dispatch = function (eventName, ComName = ' ', Value = ' ') {
let parent = this.$parent;
while (parent) {
if (ComName && parent.$options.name === ComName) {
parent.$emit(eventName, Value)
return
} else {
parent.$emit(eventName, Value)
parent = parent.$parent
}
}
}
/** * Find child node to trigger event * @param eventName * @param ComName * @param value */
Vue.prototype.$broadcast = function (eventName, ComName = ' ', value = ' ') {
let children = this.$children // Get is an array
function broadcast(children) {
for (let i = 0; i < children.length; i++) {
let child = children[i]
if (ComName === child.$options.name) {
child.$emit(eventName, value)
return
} else {
if (child.$children) {
broadcast(child)
}
}
}
}
broadcast(children)
}
Copy the code
These two methods make use of $parent and $children. Constantly fetching the parent/child node triggers the corresponding event.
My else for $dispatch says, if not for this component’s event, I also fired. Actually, that should have been deleted. Just keep looking up
use
<button @click="$parent.$emit('eat')"> </button>Copy the code
$attrs
+$listeners
The official definition of
// APP.vue <template> <div> <Test :a="1" :b="2" :c="3" :d="4" @click="click"></Test> </div> </template> <script> import Test from './test' export default { data() { return { msg: 'hello', } }, components: { Test, }, </script> // test.vue <template> <div> <h1> </h1> <ul> <li> set <strong>inheritAttrs:false </li> </li> </li> Arrts will not show the </li> {{$attrs}} <li> that has been received so that the child </li> < button@click ="$listeners. Click "> trigger the click</button> <test2 in the APP v-bind="$attrs" v-on="$listeners"></test2> </ul> </div> </template> <script> import test2 from './test2'; export default { props:['a'], name:'Test', inheritAttrs:false, </script> //test2.vue <template> <div> <h1> I am test2</h1> {{$attrs}} <button $listeners. Click </button> </div> </template> <script> export default {name: 'test2',} </script>Copy the code
Pay attention to
- When the parent passes the property in this way, it binds the property to the DOM element
props
Received will not be bound), can be used in subclassesinheritAttrs:false,
To set unbinding - Use directly when necessary
$attrs.x
/$listeners.x
use - When passing it on to the next generation, use it directly
v-bind="$attrs" v-on="$listeners"
, can be put without beingprops
Whatever is received is passed on to the next generation
$bus
A Vue instance is mounted
// app. vue <template> <div> <h1> <div> <h1> </p> <p> <p> </p> <bus></bus> </div> </template> <script> import bus from '. Export default {data() {return {MSG: 'hello',}}, mounted(){this.$bus.$emit(' listener ','hello')}, components: {bus},} </script> // $bus use <template> <div> bus {{$bus.a}} </div> </template> <script> export default {name: $bus.$on(function (value) {console.log(value)})}, // if ($bus.$on(function (value) {console.log(value)})}, BeforeDestroy (){// Unbind component this.$bus.$off(' Listening component ')}} </script>Copy the code
Vuex
Please look back
slot
<template> <div> <h1> <test1> I am the contents of the tag </test1> <h1> </h1> <p> The new version can only use template</p> <p> <test1> <! --> <! -- <div slot="header">asd</div>--> <! -- <div slot="footer">qwe</div>--> <template v-slot:header>header {{ msg }}<br></template> <template V-slot :footer>footer</template> </test1> <h1> Scope slot </h1> <p> This uses subclass data </p> <test1> <! --> <! -- <div slot="header" slot-scope="{a,b}">{{ a }}{{ b }}</div>--> <template v-slot:header="{a,b}" >{{ a }},{{ b }}</template> </test1> </div> </template> <script> import test1 from './test1'; export default { data() { return { msg: 'hello', } }, </script> // test1< template> <div> <h1> I am test1</h1> <slot></slot> <slot name="header" a="1" b="2"></slot> <slot name="footer"></slot> </div> </template> <script> export default { name: 'test1', } </script>Copy the code
This is a simple one, so I don’t want to talk about it too much. There’s a difference between the old version and the new one
- The new versionOnly can use
template
Into the heart package - The old versionYou can use
div
Etc.
conclusion
After reading the above, try writing a form component like element-UI. They use async-Validator for validation.
Making the address
Vue
There is also a simple version of Vue data responsive and compilation principle analysis and simulation of actual combat. This version does not use the virtual Dom, etc.
Virtual dom. I have also summarized an article to help you understand the virtual DOM and DOM-diff
Just a simple implementation. But some of the instructions are implemented
The full section (this time summarized, with the virtual DOM, etc.) is due to too much (too much subdivision of the title). Hard to find). I have written another article, which is still being sorted out. Probably No.1 can be released.
Post a picture to prove it. I think about it too much, so I write it slowly
vueX
I would like to recommend the simple implementation of Vuex, another article of my own. I feel that this article is relatively simple
Vuex usage
I don’t have to explain that much. Not quite skilled friends can go to see the official documents first
Give me my data definition
// store/index.js
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from '. /.. /vuex2'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
age: 10
},
strict: true.getters: {
myAge(state) {
return state.age + 30}},mutations: {
Asynchrony cannot be used in strict mode
change(state, payload) {
state.age += payload
}
},
actions: {
// Asynchronously change state
asyncChange({ commit }, payload) {
setTimeout((a)= >{
commit('change', payload)
}, 1000)}}})export default store
Copy the code
Implementation of basic Vuex
The install method
As a plug-in, Vuex first executes the install method, and we want any component to have access to this data. Components are rendered from parent to child, so we can judge first. If it is the root node, mount the property to the root node; if not, find the parent property and mount it to the Vue instance
// The official Api passes in Vue as a parameter
const install = (_vue) = >{
Vue = _vue
Vue.mixin({ // Father before son
beforeCreate() {
if (this.$options.store) { / / with a node
this.$store = this.$options.store
} else { // pass to the child node
this.$store = this.$parent && this.$parent.$store
}
}
})
}
Copy the code
Access the implementation of state
The process that we normally use is this
const store = new Vuex.Store({
state: {}})Copy the code
So we found that we actually new a VueX Store class. So let’s start writing this class.
let Vue
class Store{
constructor(options) {
this.state = options.state
this.mutations = {}
this.actions = {}
this.getters = {}
this.actions = {}
}
}
// Here is the install method
Copy the code
Use it in other components
{{$store.state.age}} <button @click="change2">=100</button> </div> </template> <script> export default { name: 'VuexUse', mounted() { console.log(this.$store) }, methods: { change2() { this.$store.state.age = 100 console.log(this.$store) } } } </script>Copy the code
You’ll see the output 10. When the button is clicked. Print again, and you’ll see that the data has changed, but the view has not been refreshed. We should let the view refresh as the data is updated. This is where we should think about using Vue’s features. So let’s modify that code
let Vue
class Store{
constructor(options) {
this.state = new Vue({ data: { state: options.state } }).state
this.mutations = options.mutations || {}
this.actions = options.actions || {}
this.getters = {}
}
}
// Here is the install method
Copy the code
So now that we’ve done the data change, we’re refreshing the view
Commit and dispatch
In VueX, changing state generally requires two methods, one synchronous and one asynchronous. Let’s implement these two methods
// When used
change() {
this.$store.commit('xxx'.10)},Copy the code
So, these two methods are written inside the Store class
let Vue
class Store{
constructor(options) {
this.state = new Vue({ data: { state: options.state } }).state
this.mutations = options.mutations || {}
this.actions = options.actions || {}
this.getters = {}
}
commit = (mutationName, payload) = >{
this.mutations[mutationName](this.state, payload)
}
dispatch = (actionName, payload) = >{
this.actions[actionName](this, payload)
}
}
Copy the code
Commit, I think everyone can understand, is to find user-defined mutations, pass in the parameters, and you can execute.
Dispatch, why pass this? The reason is that we use ES6 destruct assignment when we define it, so we pass this here
Note that you can also use both methodsCurrie,
So when you pass a value, you just pass it inpayload
It’s more convenient
Getter implementation
So the first thing we need to understand is what a getter does. Personally, I understand that there needs to be some processing for accessing data. That is, when we access this property, we get the return of this function.
let Vue
class Store{
constructor(options) {
this.state = new Vue({ data: { state: options.state } }).state
this.mutations = options.mutations || {}
this.actions = options.actions || {}
// The following is the modified part
options.getters && this.handleGetters(options.getters)
}
handleGetters(getters) {
this.getters = {}
Object.keys(getters).forEach(key= >{
Object.defineProperty(this.getters, key, {
get: (a)= >{
return getters[key](this.state)
}
})
})
}
}
Copy the code
Explain the handleGetters code
- Gets the function name for each function
- Set the corresponding return value based on the name of each function
This relatively simple code implements getters
Module function implementation
store/index
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from '. /.. /vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
age: 10
},
strict: true.getters: {
myAge(state) {
return state.age + 30}},mutations: {
change(state, payload) {
state.age += payload
}
},
actions: {
// Asynchronously change state
asyncChange({ commit }, payload) {
setTimeout((a)= >{
commit('change', payload)
}, 1000)}},modules: {
a: {
namespaced: true.state: {
num: 'a1'
},
mutations: {
Asynchrony cannot be used in strict mode
change(state, payload) {
console.log(state, payload) // is the state of the module
console.log('a')}}},b: {
state: {
num: 'b1'
},
mutations: {
Asynchrony cannot be used in strict mode
change(state, payload) {
console.log('b')}},modules: {
c: {
namespaced: true.state: {
num: 'c1'
},
mutations: {
Asynchrony cannot be used in strict mode
change(state, payload) {
console.log('c')}}}}}}})export default store
Copy the code
Now, this is going to be a little bit harder to understand. I will try my best to tell you clearly what I have learned. This section makes a lot of changes to the previous code
Let’s start with oursStore
It’s going to be the way it started
class ModuleCollection {}let Vue
class Store{
constructor(options) {
this.state = options.state
this.mutations = {}
this.actions = {}
this.getters = {}
this.modules = new ModuleCollection(options)
console.log('Collected completed modules')
console.log(this.modules)
}
}
// Here is the install method
Copy the code
Now, we need modularity, so we’ll write a method to format the data the way we want it to be
We need to register this module and continue to register it if there are subclasses below it. The core method, Reduce
ModuleCollection
@param obj @param cb */ function forEach(obj, cb) {object.keys (obj). ForEach (key=>{cb(key, cb)) obj[key]) }) } class ModuleCollection { constructor(options) { this.register([], options) } register(path, RootModule) {// Format module const rawModule = {_raw: rootModule, // original modules _children: {}, // child state: Rootmodule. state // Original data} // Two-way record The formatted data rootModule.rawModule = rawModule // Check whether the root exists if (! This. root) {this.root = rawModule} else {// The core returns a formatted module corresponding to each module const parentModel = path.slice(0, -1).reduce((root, current)=>{ console.log(current) return root._children[current] }, this.root) /----------------------------------------------------/ parentModel._children[path[path.length - 1]] = RawModule} // recursively iterates through children. Modules) {forEach(rootModule.modules, (moduleName, module)=>{ this.register(path.concat(moduleName), module) }) } } }Copy the code
Mainly explain the code above and below /————-/. The parentModel above refers to the module
- For the first time,
parentModel
isthis.root
.rawModule
isa
Module definition - The second time
parentModel
isthis.root
.rawModule
isb
Module definition - The third time
parentModel
isb
Module,rawModule
isc
Module definition
Print this.modules
Now we have formatted all the modules. The next. We need to install our formatted data. Make it accessible to them
To summarize, this function formats the modules we pass in and categorizes them.
installModule
This function iterates through the child nodes, installing the State Action Mutation getters
/** * install state action mutation getters and * @param store store in Vuex * @param rootState rootState * @param path path * @param RawModule rawModule */
function installModule(store, rootState, path, rawModule) {
/ / installation state
if (path.length > 0) { // Prove to be a child node
const parentState = path.slice(0.- 1).reduce((root, current) = >{
return rootState[current]
}, rootState)
// Official API.
// Add a property to the reactive object and make sure the new property is also reactive and triggers view updates
Vue.set(parentState, path[path.length - 1], rawModule.state)
}
// In the rawModule
// _raw Specifies the original module
/ / _children children
// state Specifies the original state
/ / install getters
// Pay attention to the use of state, use the state of this module
const getters = rawModule._raw.getters || {}
if (getters) {
forEach(getters, (getterName, getterFun)=>{
Object.defineProperty(store.getters, getterName, {
get: (a)= >getterFun(rawModule.state)
})
})
}
// mutations are similar to actions. All module functions are directly traversed when stored in the root module
const mutations = rawModule._raw.mutations || {}
if (mutations) {
forEach(mutations, (mutationName, mutationFun)=>{
// Write a publish subscribe pattern
const arr = store.mutations[mutationName] || (store.mutations[mutationName] = [])
arr.push((payload) = >{
mutationFun(rawModule.state, payload)
})
})
}
const actions = rawModule._raw.actions || {}
if (actions) {
forEach(actions, (actionName, actionsFun)=>{
const arr = store.actions[actionName] || (store.actions[actionName] = [])
arr.push((payload) = >{
actionsFun(store, payload)
})
})
}
// Iterate over the child nodes
forEach(rawModule._children, (moduleName, rawModule)=>{
Log (rawModule) // a child node
installModule(store, rootState, path.concat(moduleName), rawModule)
})
}
Copy the code
Store and rootState are always. Store in Vuex and state above the root
- For the first time,
patch
is[]
.rawModule
isThe root
Module definition - The second time
patch
is['a']
.rawModule
isa
Module definition - The third time
patch
is['b']
.rawModule
isb
Module definition- Walk in and find
b
There aremodules
, sopatch
is[' b ', 'c']
.rawModule
isc
Module definition
- Walk in and find
Implementation of a namespace
Namespaces are easy. Just prefix each method with x/
function installModule(store, rootState, path, rawModule) {
// The implementation of the namespace gets the name
let root = store.modules.root // Get the result of formatting
const nameSpace = path.reduce((str, current) = >{
root = root._children[current]
str = str + (root._raw.namespaced ? current + '/' : ' ')
return str
}, ' ')
// Install state there is no change
if (path.length > 0) {
// Prove to be a child node
const parentState = path.slice(0.- 1).reduce((root, current) = >{
return rootState[current]
}, rootState)
Vue.set(parentState, path[path.length - 1], rawModule.state)
}
// In the rawModule
// _raw Specifies the original module
/ / _children children
// state Specifies the original state
Install getters to prefix the method with a name
const getters = rawModule._raw.getters || {}
if (getters) {
forEach(getters, (getterName, getterFun)=>{
Object.defineProperty(store.getters, nameSpace + getterName, {
get: (a)= >getterFun(rawModule.state) // Use the state in the module})})}const mutations = rawModule._raw.mutations || {}
if (mutations) {
forEach(mutations, (mutationName, mutationFun)=>{
// Write a publish subscribe pattern
const arr = store.mutations[nameSpace + mutationName] || (store.mutations[nameSpace + mutationName] = [])
arr.push((payload) = >{
mutationFun(rawModule.state, payload)
})
})
}
const actions = rawModule._raw.actions || {}
if (actions) {
forEach(actions, (actionName, actionsFun)=>{
const arr = store.actions[nameSpace + actionName] || (store.actions[nameSpace + actionName] = [])
arr.push((payload) = >{
actionsFun(store, payload)
})
})
}
// Iterate over the child nodes
forEach(rawModule._children, (moduleName, rawModule)=>{
Log (rawModule) // a child node
installModule(store, rootState, path.concat(moduleName), rawModule)
})
}
Copy the code
Start with the ‘(empty) string. The root node does not require a namespace
Implementation of the registerModule API
class Store {
constructor(options) {
this.state = new Vue({ data: { state: options.state } }).state
this.mutations = {}
this.actions = {}
this.getters = {}
// Modules are collected and formatted
this.modules = new ModuleCollection(options)
console.log('Collected completed modules')
console.log(this.modules)
// Module installation and access Store rootState Path Root module Installs all modules
installModule(this.this.state, [], this.modules.root)
}
// How to write the module after it is developed
commit = (mutationName, payload) = >{
this.mutations[mutationName].forEach(fn= >fn(payload))
}
dispatch = (actionName, payload) = >{
this.actions[actionName].forEach(fn= >fn(payload))
}
/** * Custom register module * @param moduleName * @param module */
registerModule(moduleName, module) {
if (!Array.isArray(moduleName)) {
moduleName = [moduleName]
}
this.modules.register(moduleName, module)
console.log(this.modules.root)
// Install the current module
installModule(this.this.state, moduleName, module.rawModule)
}
}
Copy the code
The idea is very simple, is to register the module, after formatting. Just install it again
Note that the installation position must be determined
Auxiliary function
VueRouter
Like Vuex, I also wrote a relatively simple implementation of VueRouter. I feel that this one is relatively simple
I personally don’t think I have a good grasp of this part. So it’s not very clear. Only one idea is provided.
In the beginninginstall
methods
In our normal use, except router-link and router-View. The most commonly used is probably this.$router.push(xx). So we still do the same thing as VueX. Hang a property on each instance
const install = (Vue) = >{
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
// console.log(this) // refers to a new Vue
this._routerRoot = this // Mount the vue instance to this property
this._router = this.$options.router // The user passes in the router
// Route initialization
this._router.init(this)}else {
this._routerRoot = this.$parent && this.$parent._routerRoot
}
}
})
}
export default install
Copy the code
The above code does only two things.
- Mount attributes
- Call the initialization method of the route. Initialize the route
The mainindex
file
First we should analyze. What should we have in this master file. Import VueRouter from ‘vue-router’
so
- We should be friends
VueRoter
Class. - It has to have an initialization method on it
install
. - in
VueRoter
Of the classconstructor
In, we should process the data passed in by the user. And analyze itRouting patterns
- write
init
Method that can listen for route transformation and then jump to the corresponding route. Render the corresponding component
After the analysis. So we started writing
Let me just show you the big picture
import install from './install'
import createMatcher from './createMatcher'
import HashHistory from './history/hash'
class VueRouter {
constructor(options) {
// Matcher matcher handles the tree structure and flattens it
// Returns the results of the two methods addStore match
this.matcher = createMatcher(options.routes || [])
// Hash history is used internally to initialize the route
// Base indicates the base class. The notification methods for all routes are placed on the base class to ensure that different routing apis have the same usage methods
this.history = new HashHistory(this)
}
push(location) {
}
init(app) {
// App is the top-level Vue instance
// Get the path, jump and render the component
// After the match is completed, the listening path changes, and the update operation is completed
}
}
VueRouter.install = install
export default VueRouter
Copy the code
CreateMatcher method
The methods that appear in it are explained below
import createRouteMap from './createRouteMap'
import { createRoute } from './history/base'
export default function createMatcher(routes) {
// Start flattening the data
const { pathList, pathMap } = createRouteMap(routes)
/ / reload s
function addRoute(routes) {
createRouteMap(routes, pathList, pathMap)
}
function match(location) {
console.log('Create match inside' + location)
// Location obtained from pathMap
const record = pathMap[location]
// console.log(record)
return createRoute(record, {
path: location
})
}
return {
addRoute, match
}
}
Copy the code
We use the createRouteMap method to format the routes passed in (i.e. the configuration passed in by the user). We get a pathList(address list) and pathMap(address map, which contains addresses, components, etc.).
In the official API, there’s an addRotes, which is adding another set of routes.
Let’s use the createRouteMap method again. What does this method look like
The match method matches the incoming location(address). Returns the corresponding record
CreateRouteMap method
export default function createRouteMap(routes, oldPathList, oldPathMap) {
const pathList = oldPathList || []
const pathMap = oldPathMap || Object.create(null)
// Object.create(null) differs from {} the former has no prototype chain
// Array flattening
routes.forEach(route= >{
addRouteRecord(route, pathList, pathMap)
}
)
return {
pathList, pathMap
}
}
function addRouteRecord(route, pathList, pathMap, parent) {
const path = parent ? parent.path + '/' + route.path : route.path
const record = {
path,
component: route.component,
parent
// todo
}
if(! pathList[route]) { pathList.push(path) pathMap[path] = record }if (route.children) {
route.children.forEach(route= >{
addRouteRecord(route, pathList, pathMap, record)
})
}
}
Copy the code
Object.create(null) differs from {} the former has no prototype chain
{} will have a bunch of attributes
AddRouteRecord is the core method for this. What it does is
- Let’s find the parent element first. If you have. Plus the parent for example
about/a
. Nothing is itself - A record is then generated
record
- Judge what you pass in
Route (i.e., each route)
Whether inpathList
The inside. If you do, skip. If it’s not there, add it. ** this method is implementedaddRoutes
The role of * * - Recursive traversal. If any children continue to add
CreateRoute method
This method and its key !!!!
Reason: For example, we render the component of the about/ A path. Do we have to render about so that we can render A?
So the main function of this method is. Save the path’s parents as well
export function createRoute(record, location) {
const res = [] // If it matches a path, put it in
if (record) {
while (record) {
res.unshift(record)
record = record.parent
}
} // Save the parent path
console.log(res, location)
return {
...location,
matched: res
}
}
Copy the code
The history method
This.history = new HashHistory(this)
Why list them separately? Because there are different routing modes, but there is a common approach. Of course, there are also different ways to handle different routes.
We’re just thinking about hash here
base.js
export function createRoute(record, location) {
const res = [] // If it matches a path, put it in
if (record) {
while (record) {
res.unshift(record)
record = record.parent
}
} // Save the parent path
console.log(res, location)
return {
...location,
matched: res
}
}
class History {
constructor(router) {
this.router = router
this.current = createRoute(null, {
path: '/'// The default path
})
}
transitionTo(location, cb) { // It is better to mask it to prevent multiple calls
console.log(location, cb)
// The path starts matching the corresponding template
const r = this.router.match(location)
this.current = r // Update the current path
// eslint-disable-next-line eqeqeq
if (location === this.current.path && r.matched.length === this.current.matched) {
return
}
this.cb && this.cb(r)
cb && cb()
}
setupListeners() {
window.addEventListener('hashchange', () = > {this.transitionTo(window.location.hash.slice(1))
})
}
listen(cb) {
this.cb = cb
}
}
export default History
Copy the code
You can see that this base.js does several things
- A default route was initialized
- Provides a jump method
- Route changes were monitored
listen
We’ll talk about that later
If judgment in transitionTo. This is to prevent multiple calls.
hash.js
import History from './base'
function ensureSlash() {
if (window.location.hash) {
return
}
window.location.hash = '/'
}
class HashHistory extends History {
constructor(router) {
super(router) // super === parent. Call (this) passes router to parent
this.router = router
ensureSlash() // Make sure there is a hash value
}
getCurrentLocation() {
return window.location.hash.slice(1) // Except for the path after #}}export default HashHistory
Copy the code
This is a little bit easier. I won’t explain it
Go back to index.js
import install from './install'
import createMatcher from './createMatcher'
import HashHistory from './history/hash'
class VueRouter {
constructor(options) {
// Matcher matcher handles the tree structure and flattens it
// Returns the results of the two methods addStore match
this.matcher = createMatcher(options.routes || [])
// Hash history is used internally to initialize the route
// Base indicates the base class. The notification methods for all routes are placed on the base class to ensure that different routing apis have the same usage methods
this.history = new HashHistory(this)
}
match(location) { // Returns a matching result
return this.matcher.match(location)
}
push(location) {
this.history.transitionTo(location, ()=>{
window.location.hash = location// Render twice with transitionTo and hash listener
}) // Hash does not change to change hash
}
init(app) {
// App is the top-level Vue instance
// console.log(app)
// Get the path, jump and render the component
// After the match is completed, the listening path changes, and the update operation is completed
const history = this.history
const setupHashListener = (a)= >{ // Callback after listening
history.setupListeners() // Listen for route changes parent class
}
history.transitionTo( // Jump method parent class
history.getCurrentLocation(), // Get the current path shunt so it is a subclass
setupHashListener
)
// Subscribe and update this method with route property changes
history.listen((route) = >{
app._route = route
})
}
}
VueRouter.install = install
export default VueRouter
Copy the code
What index.js does after it’s been modified,
- Listen for routes.
- Forward route.
- Set the change
_route
Is a function of_route
Not yet dynamic)
Return to the install
import RouterView from './components/router-view'
import RouterLink from './components/router-link'
const install = (Vue) = >{
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
// console.log(this) // refers to a new Vue
this._routerRoot = this
this._router = this.$options.router // The user passes in the router
// Route initialization
this._router.init(this)
// define current as responsive. Data changes refresh the view
console.log(this._router)
// Create a _route attribute for the current instance, taken from this._router.history.current
Vue.util.defineReactive(this.'_route'.this._router.history.current)
// Update _route after definition
} else {
this._routerRoot = this.$parent && this.$parent._routerRoot
}
}
})
Object.defineProperty(Vue.prototype, '$route', {
get() {
console.log(this._routerRoot._route)
return this._routerRoot._route
}
})
Object.defineProperty(Vue.prototype, '$router', {
get() {
return this._routerRoot._router
}
})
Vue.component('RouterView', RouterView)
Vue.component('RouterLink', RouterLink)
}
export default install
Copy the code
Go back to the install method, after initialization. Set _route to dynamic (get and set).
Then the data changes and the view is refreshed.
component
RouterView
export default {
functional: true.// Functional components have no state
render(h, { parent, data }) { // There are a lot of options in it
// console.log(options)
const route = parent.$route // Is placed on the vue prototype
console.log(route)
let depth = 0
// $vnode represents the placeholder vnode
while (parent) {
if (parent.$vnode && parent.$vnode.data.routerView) {
depth++
}
parent = parent.$parent
}
data.routerView = true
const record = route.matched[depth]
console.log(record)
if(! record) {return h()
}
return h(record.component, data)
}
}
Copy the code
The hardest thing to understand in this code is depth.
Route is an attribute. This code is in history/base.js. CreateRoute returns a match. This is where all the parent paths are stored
Explanation of the routerView. Custom properties. See if it’s the root node. The first time you come in, render the App component (with the RouterView in it). If so, it is the next node to render
The router is a way
RouterLink
export default {
props: {
to: {
type: String.require: true
},
tag: {
type: String.default: 'a'}},methods: {
handle() {
this.$router.push(this.to)
}
},
render(h) {
const tag = this.tag
return <tag onclick = { this.handle } > { this.$slots.default } < /tag>}}Copy the code
I’m using JSX syntax here. RouterLink. Vue (RouterLink. Vue
Hook function (route guard)
Routing index
. router.beforeEach((to, from, next) = >{
console.log(1)
setTimeout((a)= >{
next()
}, 1000)
})
router.beforeEach((to, from, next) = >{
console.log(2)
setTimeout((a)= >{
next()
}, 1000)})export default router
Copy the code
Simple ideas
This is more like express and KOA.
So that’s the idea.
/ / store
let deps = []
/ / place
beforeXxx(cb){
this.deps.push(cb)
}
/ / use
// Before the view is updated or jumped
this.deps.forEach(dep= >{
dep()
})
Copy the code
BeforeEach implementation
- In the first place now
The index. js
Under folder (VueRouter/index
) do the initialization
I’m just going to use the ellipsis
class VueRouter {
constructor(options) {
....
this.beforeEachs = []
}
match(location) { // Returns a matching result. } push(location) { .... } init(app) { .... } beforeEach(cb) {this.beforeEachs.push(cb)
}
}
Copy the code
Store all beforeEach first. Perform it before the jump
Go to the history/ Base folder and find the transitionTo method. These three lines of code in it are jump routes
this.current = r // Update the current path
this.cb && this.cb(r)
cb && cb()
Copy the code
How does the following code implement running beforeEach
// 1. Fetch the queue we collected
const queue = this.router.beforeEachs
// Auxiliary functions
const iterator = (hook, next) = >{
hook(r, this.current, next)
}
// Run queue
runQueue(queue, iterator, ()=>{
this.upDateRoute(r, cb)
})
// This method encapsulates the above three update methods
upDateRoute(r, cb) {
this.current = r
this.cb && this.cb(r)
cb && cb()
}
The runQueue function definition is actually a recursive call
function runQueue(queue, iterator, callback) {
function step(index) {
if (index === queue.length) {
return callback()
}
const hook = queue[index]
iterator(hook, ()=>step(index + 1))
}
step(0)}Copy the code
Ideas:
- Get the collected
beforeEach
The queue - Let the queue execute in sequence
- Why not?
for
.JS single-threadedwithfor
Asynchrony is a problem
The complete code
/ / history/bsae. js
export function createRoute(record, location) {... }function runQueue(queue, iterator, callback) {
function step(index) {
if (index === queue.length) {
return callback()
}
const hook = queue[index]
iterator(hook, ()=>step(index + 1))
}
step(0)}class History {
constructor(router) {
...
}
transitionTo(location, cb) {
console.log(location, cb)
const r = this.router.match(location)
if (location === this.current.path && r.matched.length === this.current.matched) {
return
}
const queue = this.router.beforeEachs
const iterator = (hook, next) = >{
hook(r, this.current, next)
}
runQueue(queue, iterator, ()=>{
this.upDateRoute(r, cb)
})
}
upDateRoute(r, cb) {
this.current = r // Update the current path
this.cb && this.cb(r) cb && cb() } setupListeners() { ... } listen(cb) { ... }}export default History
Copy the code
conclusion
And when that happens. Several other hook functions can also be implemented. In much the same way. That is, through the callback function
Routing permissions
Recommend the article that spends underpants big guy to write authority to land. Hand to hand, take you to use vUE masturbation background series two (login permission)
This is the best article I have read about routing permissions
I am working on my own summary
Vue3
proxy
Ruan Yifeng teacher this book about this part has been written very well. I’m not going to talk about it any more. Click here for details
let obj = {
name: {
achen: {
name: 'he had o'.age: 22,}},sex:'male'.arr: ['eat'.'喝'.'play'],}let handler = {
// Target is the original object and key is the key
get(target, key) {
// The lazy proxy will only fire if it gets the object, and will not fire if it does not
if (typeof target[key]=== 'object') {// recursive call
return new Proxy(target[key],handler)
}
console.log('collect')
// return target[key] old method
return Reflect.get(target,key)
},
set(target, key, value) {
console.log('Trigger update')
let oldValue = target[key]
console.log(oldValue,value,key)
if(! oldValue){console.log('Set properties')}else if(oldValue! ==value){console.log('Modify Properties')}// target[key] = value
// Returns a value
return Reflect.set(target,key,value)
},
}
// Poor compatibility, but can proxy 13 methods
// defineProperty He can only intercept specific attributes
// Intercepts the entire object
let proxy = new Proxy(obj,handler)
// proxy.sex = 'nv'
// console.log(proxy.sex)
/ / array
// proxy.arr. Push (132) // go to obj and collect push length
// proxy.arr[0] = 100
proxy.xxx = 100
Copy the code
About Reflect
Developer.mozilla.org/zh-CN/docs/…