Vue
3
Vue.js v3 guide
Essentials
Creating an Application
import { createApp } from 'vue'
const app = createApp({
/* root component options */
})
单独 App.vue
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
Mount 挂载
<div id="app"></div>
app.mount('#app')
App 配置
app.config.errorHandler = (err) => {
/* handle error */
}
App 范围内组件
app.component('TodoDeleteButton', TodoDeleteButton)
这个组件可以在 App 的任何地方使用。
多个 App 实例
const app1 = createApp({ /* ... */ })
app1.mount('#container-1')
const app1 = createApp({ /* ... */ })
app2.mount('#container-2')
Template Syntax
文本插值
<span>Message: {{ msg }}</span>
原始 HTML
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
`v-html` Vue 指令的一种。
Note that you cannot use
v-htmlto compose template partials, because Vue is not a string-based templating engine. Instead, components are preferred as the fundamental unit for UI reuse and composition.
属性绑定
<div v-bind:id="dynamicId"></div>
<!--- v-bind:id 可简写为 :id --->
<button :disabled="isButtonDisabled">Button</button>
v-bind(用于无法使用 {{}} 的 HTML 属性中) 使 id 与组件同步,如果 id 为 null/undefined,则渲染后的页面无属性 id。
动态绑定多个属性
const objectOfAttrs = {
id: 'container',
class: 'wrapper',
}
<div v-bind="objectOfAttrs"></div>
JS 表达式(支持数据绑定的 JS 表达式,可用于文本插值、属性绑定,只能键入表达式,可以调用组件中暴漏的函数,此处的表达式可使用的["全局对象"](https://github.com/vuejs/core/blob/main/packages/shared/src/globalsWhitelist.ts)[但是,可以通过 `app.config.globalProperties` 显示添加])。
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>
<span :title="toTitleDate(date)">
{{ formatDate(date) }}
</span>
指令( `v-` 前缀的属性,参数[动态参数值约束、动态参数语法约束、避免属性名大写],修饰语 `@submit.prevent <`> event.preventDefault()= )
内建属性:v-text, v-html, v-show, v-if, v-else, v-else-if, v-for, v-on, v-bind, v-model, v-slot, v-pre, v-once, v-memo, v-cloak
<a v-bind:href="url"> ... </a>
<a :href="url"> ... </a>
<a v-on:click="doSomething"> ... </a>
<a @click="doSomething"> ... </a>
<a v-bind:[attributeName]="url"> ... </a>
<a :[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
<a @[eventName]="doSomething"> ... </a>
<form @submit.prevent="onSubmit"> ... </form>
Reactivity Fundamentals
声明响应式状态
import { reactive } from 'vue'
const state = reactive({ count: 0 )}
- 使用
<script setup>与否的对比
<!--- 不使用 --->
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function incrment() {
state.count++
}
return {
state,
increment
}
}
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
<template>
<!--- 使用 --->
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
- DOM Update Timing
<script setup>
import { reactive, nextTick } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
nextTick(() => {
console.log(state.count)
})
}
</script>
<template>
<div>
{{ state.count }}
</div>
<button @click="increment">
BUTTON
</button>
</template>
Deep Reactivity
In Vue, state is deeply reactive by default.
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
obj.nested.count++
obj.arr.push('baz')
}
- Reactive Proxy vs. Original
import { reactive } from 'vue'
const raw = {}
const proxy = reactive(raw)
// proxy is NOT equal to the original
console.log(proxy === raw)
console.log(proxy === reactive(raw))
console.log(proxy === reactive(proxy))
import { reactive } from 'vue'
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw)
reactive()的限制数据类型仅限于对象
无法替换响应式(代理)对象,一旦替换原来的响应式对象会失去连接
import { reactive } from 'vue'
const state = reactive({ count: 0})
let n = state.count
n++
console.log(n)
let { count } = state
count++
// won't be able to track changes to state.count
callSomeFunc(state.count)
通过
ref()声明的响应式变量为了解决 `reactive()` 的局限性,Vue 提供了 `ref()` 用于对任何数据类型创建响应式。
import { ref } from 'vue'
const count = ref(0)
console.log(count)
console.log(count.value)
count.value++
console.log(count.value)
和对象中的属性类似,一个 ref 的 `.value` 属性也是响应式的。另外,当值为对象类型时,会用 `reactive()` 自动转换它的 `.value` 。
ref 保存对象
import { ref } from 'vue'
const objectRef = ref({ count: 0 })
objectRef.value = { count: 1 }
console.log(objectRef.value) // Proxy
Refs 也可传入函数、从纯对象中结构却不失去响应状态
const obj = {
foo: ref(1),
bar: ref(2)
}
callSomeFunction(obj.foo)
const { foo, bar } = obj
- ref 在模板中的解包
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!--- no .value needed --->
</button>
</template>
<script setup>
import { ref } from 'vue'
const object = { foo: ref(1) }
function increment() {
object.foo.value++
}
</script>
<template>
<button @click="increment">
{{ object.foo }} <!--- no .value needed --->
</button>
</template>
<script setup>
import { ref } from 'vue'
const object = { foo: ref(1) }
</script>
<template>
<button>
- {{ object.foo + 1 }} <!--- but this need .value --->
+ {{ object.foo.value + 1 }}
</button>
</template>
<!--- 让 foo 成为顶级属性 --->
<script setup>
import { ref } from 'vue'
const object = { foo: ref(1) }
const { foo } = object
</script>
<template>
<button>
{{ foo + 1 }} <!--- no .value needed --->
</button>
</template>
- ref 在响应式对象中的解包
import { ref, reactive } from 'vue'
const count = ref(0)
const state = reactive({
count
})
console.log(state.count)
state.count = 1
console.log(state.count)
如果新的 ref 赋给了 state.count 则原来的会断开连接
import { ref, reactive } from 'vue'
const count = ref(0)
const otherCount = ref(2)
const state = reactive({
count
})
console.log(state.count)
state.count = 1
console.log(state.count)
state.count = otherCount
console.log(state.count)
console.log(count.value)
Ref unwrapping only happens when nested inside a deep reactive object.
ref 在数组、集合中的解包
import { ref, reactive } from 'vue'
const books = reactive([ref('Vue 3 Guide')])
// 需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 需要 .value
console.log(map.get('count').value)
Computed Properties
基本例子
如果使用包含响应式数据的复杂逻辑的话,推荐使用 `computed()` 函数。
<script setup>
import { computed, reactive } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2',
'Vue 3',
'Vue 4'
]
})
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
Computed Caching vs. Methods
如果不使用 `computed()` 函数,还可以用方法函数。
<script setup>
import { reactive } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2',
'Vue 3',
'Vue 4',
'Vue 5'
]
})
const publishedBooksMessage = () => {
return author.books.length > 3 ? 'Yes' : 'No'
}
</script>
<template>
<p>Has published books?</p>
<span>{{ publishedBooksMessage() }}</span>
</template>
两种方法都能产生最终结果,但是 computed properties are cached based on their reactive dependencies。计算属性在 author.books 不改变的情况下,不发生变化。This means as long as `author.books` has not changed, multiple access to `publishedBooksMessage` will immediately return the previously computed result without having to run the getter function again.
This also means the following computed property will never update, because `Date.now()` is not a reactive dependency:
const now = computed(() => Date.now())
与计算属性不同,每当页面重绘时,方法总会运行。
可写计算属性
计算属性默认 getter-only,如果对计算属性赋新值,会有运行时警告。
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return firstName.value + ' ' + lastName.value
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
console.log(fullName.value)
fullName.value = 'Evan You'
console.log(fullName.value)
</script>
- 最佳实践
- Getters should be side-effect free
- Avoid mutating computed value
Class and Style Bindings
数据绑定的常见应用就是操作元素的类列表和行内样式。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为
class和style的v-bind用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。Binding HTML Classes
Binding to Objects
<div :class="{ active: isActive }"></div>
还可有多个 class 属性,以及在一个 class 属性中添加多个对象属性。
<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
</script>
<template>
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
</template>
<!--- 模板部分会渲染成 --->
<div class="static active"></div>
<!--- 如果 hasError 为真,则渲染成 --->
<div class="static active text-danger"></div>
对象绑定不必是行内:
<script setup>
import { reactive } from 'vue'
const classObject = reactive({
active: true,
'text-danger': false
})
</script>
<template>
<div :class="classObject"></div>
</template>
还能绑定到计算属性:
<script setup>
import { ref, computed } from 'vue'
const isActive = ref(false)
const error = ref(true)
const classObject = computed(() => ({
active: isActive.value && !error.value,
'text-danger': error.value
}))
</script>
<template>
<div :class="classObject"></div>
</template>
- Binding to Arrays
<script setup>
import { ref } from 'vue'
const activeClass = ref('active')
const errorClass = ref('text-danger')
</script>
<template>
<div :class="[activeClass, errorClass]"></div>
</template>
<!--- 模板中的 binding class 可改成(2种写法) --->
<div :class="[isActive ? activeClass : '', errorClass]"></div>
<div :class="[{ active: isActive }, errorClass]"></div>
With Components
组件内部对一个元素添加 class,使用组件时又添加一次,两次的 class 会合并。可以用 `$attrs.class` 只应用”使用组件”时的 class。Binding Inline Styles
对象
<script setup>
import { ref } from 'vue'
const activeColor = ref('red')
const fontSize = ref(30)
</script>
<template>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">nihao</div>
</template>
<!--- 其他表达方式 --->
<div :style="{ 'font-size': fontSize + 'px' }">
const styleObject = reactive({
color: 'red',
fontSize: '13px'
})
<div :style="styleObject">
- 数组
<div :style="[baseStyles, overridingStyles]">
- 自动前缀,最大浏览器支持
- 多个值在一起,渲染最后的浏览器支持的值
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
在这里,渲染结果 `display: flex` 。
Conditional Rendering
条件渲染,命令:v-if, v-else, v-else-if, v-show, v-for
v-else 必须和 v-if/v-else-if 搭配使用
v-show 所在元素会被渲染成 DOM 树;v-show 只是在切换 CSS display 属性;不支持用在
<template>,也不和 v-else 搭配使用。
<button @click="awe = !awe">Toggle</button>
<h1 v-if="awe">You are good</h1>
<h1 v-else>You are not good</h1>
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
<template v-if="ok">
...
</template>
<h1 v-show="ok">Hello!</h1>
- v-if vs. v-show
v-if 是真正的条件渲染,在条件变换时,条件块内部的元素会不断销毁和重新创建
v-if 是懒加载的,如果初始条件为 false,什么都不会做,只有条件第一次变为真时才会开始销毁重建过程
相对看,v-show 更简单,它的条件块总是渲染,显示与否依靠的是 CSS 的属性值切换
两者综合来看:
v-if 与 v-show 相比,切换成本更高,因此,如果需要经常切换某种状态,使用 v-show,如果状态不太经常改变,使用 v-if
v-if with v-for
不推荐用在一起,解释文档:
List Rendering
v-for
<script setup>
import { ref } from 'vue'
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo'}, { message: 'Bar'}])
</script>
<template>
<ul>
<li v-for="({ message }, index) of items">
{{ parentMessage }} - {{ index + 1 }} - {{ message }}
</li>
</ul>
</template>
- v-for + Object
<script setup>
import { ref, reactive } from 'vue'
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'John Doe',
publishedAt: '2022-09-13'
})
</script>
<template>
<ul>
<!--- value, key, index 的顺序的改变会影响结果 --->
<li v-for="(value, key, index) of myObject">
{{ index }} - {{ key }} - {{ value }}
</li>
</ul>
</template>
- v-for with Range
<template>
<span v-for="n in 10">
<span v-if="n === 10">{{ n }}</span>
<span v-else>{{ n }}, </span>
</span>
</template>
- v-for on
<template>
<script setup>
import { ref } from 'vue'
const items = ref([{ msg: 'hello'}, {msg: 'hell'}, {msg: 'hel'}, {msg: 'he'}, {msg: 'h'}])
const textColor = ref('#904cbc')
</script>
<template>
<ul>
<template v-for="item in items">
<li :style="{ color: textColor }">{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
</template>
v-for + v-if
说是不推荐,我看看原因
错误用法:
<template>
<ul>
<li v-for="todo in todos" v-if="!todo.isComplete">{{ todo.name }}</li>
</ul>
</template>
Fixed:
<template>
<ul>
<template v-for="todo in todos">
<li v-if="!todo.isComplete">{{ todo.name }}</li>
</template>
</ul>
</template>
key ——维持状态/条件
更新列表的默认模式只,适用于列表输出不依靠子组件状态或有限 DOM 状态
<script setup>
import { ref } from 'vue'
const items = ref([{ msg: 'hello'}, {msg: 'hell'}, {msg: 'hel'}, {msg: 'he'}, {msg: 'h'}])
</script>
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.msg }}
</li>
</ul>
</template>
当使用 `<template v-for>` 时,key 应位于 `<template>` 容器。
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
每当用 v-for 时都加上 key。
key 绑定原始类型------字符串或数字,而非对象。
- v-for + Component
<MyComponent v-for="item in items" :key="item.id" />
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
Component 不会自动将 item 值插入,这样利用复用组件。
数组变化检测
用到的方法:push, pop, shift, unshift, splice, sort, reverse。它们会改变元数组;而 filter、concat、slice 不改变,会返回新数组。
展示过滤后/分类后的结果
<script setup>
import { ref, computed } from 'vue'
const numbers = ref([1,2,3,4,5])
const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})
</script>
<template>
<li v-for="n in evenNumbers">{{ n }}</li>
</template>
在可计算属性中使用 reverse(),sort() 会改变原有数组,在使用前要对其进行拷贝。
- return numbers.reverse()
+ return [...numbers].reverse()
Event Handling
监听事件
v-on 简写成 @,用法:
v-on:click"handler"= /@click"handler"= 。handler 值可以是以下两种:
- 行内 handlers
- 方法 handlers
- 行内 handlers
<script setup>
import { ref } from 'vue'
const counter = ref(0)
</script>
<template>
<button @click="counter++">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</template>
- 方法 handlers
<script setup>
import { ref } from 'vue'
const name = ref('Vue.js')
function greet(event) {
alert(`Hello ${name.value}!`)
// `event` is the native DOM event
if (event) {
alert(event.target.tagName)
}
}
</script>
<template>
<button @click="greet">Greet</button>
</template>
方法 vs. 行内
模板编译器的检测标准:如果检测到确定的函数则是方法,如果检测到 `foo()` , `count++` 则是行内。
- 行内 handlers 调用方法
<script setup>
function say(msg) {
alert(msg)
}
</script>
<template>
<button @click="say('Hello!')">SAY</button>
</template>
访问行内 handlers 的事件参数
需要访问原始 DOM 事件
<script setup>
function warn(msg, event) {
if (event) {
event.preventDefault()
}
alert(msg)
}
</script>
<template>
<!--- 2 种方式 --->
<button @click="warn('Form cannot be submitted yet.', $event)">Submit</button>
<button @click="(event) => warn('Form cannot be submitted yet.', event)">Submit</button>
</template>
- 事件修改器
<a @click.stop="doThis"></a>
<form @submit.prevent="onSubmit"></a>
<a @click.stop.prevent="doThat"></a>
顺序也是重要的,stop 和 prevent 的先后顺序
<form @submit.prevent></form>
<div @click.self="doThat">...</div>
<div @click.capture="doThis">...</div>
<a @click.once="doThis"></a>
<div @scroll.passive="onScroll">...</div>
.passive 和 .prevent 不能一起用,两者起相反效果。
- 键修改器
<input @keyup.enter="submit" />
<input @keyup.page-down="onPageDown" />
常用 key:
.enter
.tab
.delete
.esc
.space
.up
.down
.left
.right
系统 key 修改器:.ctrl
.alt
.shift
.meta
.exact 修改器
- 鼠标修饰符
- .left
- .right
- .middle
Form Input Bindings
基本用法
1.1 Text
<script setup>
import { ref } from 'vue'
const txt = ref('')
</script>
<template>
<input v-model="txt" />
<p>
Message is: {{ txt }}
</p>
</template>
1.2 Multiline Text
<script setup>
import { ref } from 'vue'
const txt = ref('')
</script>
<template>
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ txt }}</p>
<textarea v-model="txt"></textarea>
</template>
别用 `<textarea>{{ txt }}</textarea>`
1.3 Checkbox
<script setup>
import { ref } from 'vue'
const checked = ref(false)
</script>
<template>
<input type="checkbox" id="checkbox" v-model="checked"/>
<label for="checkbox">{{ checked }}</label>
</template>
多个 checkbox
<script setup>
import { ref } from 'vue'
const checkedNames = ref([])
</script>
<template>
<div>Checked names: {{ checkedNames }}</div>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>
</template>
1.4 Radio
<script setup>
import { ref } from 'vue'
const picked = ref('One')
</script>
<template>
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
</template>
1.5 Select
Single
<script setup>
import { ref } from 'vue'
const picked = ref('One')
</script>
<template>
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
</template>
Multiple
<script setup>
import { ref } from 'vue'
const selected = ref([])
</script>
<template>
<div>Selected: {{ selected }}</div>
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</template>
<style>
select[multiple] {
width: 100px;
}
</style>
v-for 动态渲染
<script setup>
import { ref } from 'vue'
const selected = ref('A')
const options = ref([
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
])
</script>
<template>
<select v-model="selected">
<option v-for="option in options" :value="option.value">
{{ option.text }}
</option>
</select>
<div>Selected: {{ selected }}</div>
</template>
Value Bindings
将值和动态属性进行绑定。
2.1 Checkbox
<input
type="checkbox"
v-model="toggle"
:true-value="dynamicTrueValue"
:false-value="dynamicFalseValue" />
2.2 Radio
<input type="radio" v-model="pick" :value="first">
<input type="radio" v-model="pick" :value="second">
2.3 Select Options
<select v-model="selected">
<option :value="{ number: 123 }">123</option>
</select>
Modifiers
3.1 .lazy 3.2 .number 3.3 .trim
<input v-model.lazy="msg">
<input v-model.number="age">
<input v-model.trim="msg">
v-model + Component
见后续”组件”部分。
Lifecycle Hooks
每个 Vue 组件实例创建时会经历一系列初始化步骤。比如,建立数据观测系统、编译模板、挂载实例到 DOM、当数据改变时更新 DOM。随着一系列过程,还会有生命周期挂钩的函数。让用户能够在指定阶段添加自己的代码。
注册生命周期钩子
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`The component is now mounted.`)
})
</script>
与 onMounted 一样常用的生命周期 Hooks,有 onUpdated, onUnmounted。
此类 Hooks 使用条件:调用栈是同步的,创建于 setup() 内部。
Watchers
基本例子
watch()函数:监听一个或多个响应式数据源,当数据变化时,触发回调函数。Deep Watchers
使用的时候注意性能损耗。watchEffect()
watch() 和 watchEffect() 的区别:
- watch 只监听显式的可监听源。不会追踪接入回调函数内部的事物。另外,回调函数只在源真的改变时才触发。
- watchEffect 将跟踪依赖和边际效果结合在一起。自动跟踪每个接入的响应式属性(在同步执行过程中)。更方便,代码更简洁,但不容易看出响应式依赖。
Cackback Flush Timing
默认情况下,用户创建的 watcher 回调函数在 Vue 组件更新以前被调用。 如果想在 Vue 组件更新以后,在 watcher 回调函数中改变 DOM。需要指定 `flush: 'post'` 。
// watch
watch(source, callback, {
flush: 'post'
})
/// watchEffect
// way 1
watchEffect(callback, {
flush: 'post'
})
// way 2
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* executed after Vue updates */
})
Stopping a Watcher
Template Refs
Scaling Up
https://vuejs.org/guide/scaling-up/sfc.html
SFC
Tooling
- Create Vue project
- Vue CLI(webpack)
- create-vue(vite)
Browser Devtools
Testing
- Cypress
- Vitest
- Jest
- Linting
- eslint-plugin-vue
- Formatting
- Volar
- Prettier
Routing
- vue-router@4(vue3)
- vue-router@3(vue2)
State Management
- Reactivity API
- Pinia
Testing
- Unit | Vitest, Jest
- Component | Vitest, Cypress
- End-to-end | Cypress, Playwright
SSR: basic, nuxt, quasar, vite-ssr
Hydration(wikipedia):In web development, hydration or rehydration is a technique in which client-side JavaScript converts a static HTML web page, delivered either through static hosting or server-side rendering, into a dynamic web page by attaching event handlers to the HTML elements.
相关:js-basics