setup
setup 中的代码与普通的 <script>
只在组件被首次引入的时候执行一次不同,<script setup>
中的代码会在每次组件实例被创建的时候执行。 所以,任何在 <script setup>
声明的顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用 。有方法通过 import 导入,放在 setup 中,就无需通过 methods 暴露,可以直接用
HOC
- 初期(黑命贵之前)的 react 通过 React.createdClass 方法创建组件,可以加 mixins 属性,和 vue 一样,后来不用了。后来还有 Component.prototype[key] = mixin[key]这种继承的方式。
- 换成后来这种 hoc(com)的方式后,需要注意包装顺序,内层的 hoc 距离原组件最近,然后逐层向外。
- hoc 如果不用传参,就像 withRouter,只需要写一层 return 就行。如果参数多,类似 mapStateToProps 等,就可能会 return 多次,一层 return 套着一层 return,其实没事区别。
- 写 hoc 时,也有两种写法。一个是正向属性代理:
class Index extends React.Component{
render(){
return <div> hello,world </div>
}
}
Index.say = function(){
console.log('my name is alien')
}
function HOC(Component) {
return class wrapComponent extends React.Component{
render(){
return <Component { ...this.props } { ...this.state } />
}
}
}
const newIndex = HOC(Index) console.log(newIndex.say)
这个比较常见,还有一个是反向的组件继承:
class Index extends React.Component{
render(){
return <div> hello,world </div>
}
}
Index.say = function(){
console.log('my name is alien')
}
function HOC(Component) {
return class wrapComponent extends Component{
}
}
const newIndex = HOC(Index) console.log(newIndex.say)
第一种是想组件里加东西,第二种直接继承。
vue 新版路由主要变化
new Router 改为 createRouter。mode 的变化 。base 属性改到了 createWebHistory 的第一个参数。通配符路由没了,改为正则,404 的判断是 path: '/:path(.*)*'
新版 vue 父组件调用子组件
- 首先用 ref 引用子组件<Son ref="sonRef"></Son>,通过 const sonRef = ref(null)引用
- 子组件可以用之前的选项式 api 的方式,默认暴露整个组件的内容。在 setup 方式的组件中,默认是关闭的,引用组件后不会暴露 setup 中的内容,所以需要 defineExpose 方法,defineExpose({ prop1, prop2, fn1, fn2 }),指定那些属性和方法需要暴露。
pinia 的使用
- 与 vuex 的不同,通过 defineStore 来创建 Store,第一个参数是 id,这是唯一的,用来将 store 连接到 devtools。store 中有 state、getter、action。
- 在项目的入口文件 main.js 或者 main.ts 中通过 createPinia 方法创建一个 Pinia 实例,然后 app.use()
- 在 store 目录下可以创建多个文件,对应多个 store。
Pinia
中没有 module
的概念,是一个拍平的 store
结构。Pinia
推荐按照功能去划分一个个的 store
,这样更方便管理和使用。 一个文件一个 store,通过 defineStore 方法创建,对应的函数名的风格是 useXXXStore,第一个参数是 id,后面是 state、getter、action
- pinia的持久化存储,除了自己写也可以用插件,比如pinia-plugin-persistedstate。先安装,然后pinia.use(piniaPluginPersistedstate),在defineStore时配置persist选项,直接写true或者写对象来控制,其实还是存在本地了。
const useUserStore = defineStore('user', {
// 也可以直接写 persist: true
persist: {
key: "USER",
storage: sessionStorage,
paths: ["token"]
}
})
- 总结一下发展历程:
- vuex 的 commit(mutation),在 mutation 中直接修改 state
- redux 中 dispatch(action),在 reducer 中 return 新的 state,“只换不修”
- pinia 和 rtk 中的直接修改 state 中的属性,通过代理对象拦截赋值操作,看起来是可修改的,其实不可修改。
RTK-redux toolkit
这是 redux 的工具包,简化了 redux,或者说标准化了 redux。核心概念仍然是 store、state、reducer、action。
rtk 有两个关键方法,configureStore 和 createSlice。
import { createSlice, configureStore } from '@reduxjs/toolkit'
const initialState = { name: "coderlzw", age: 20 }
const userReducer = createSlice({
name: "user",
initialState: initialState,
reducer: {
setName (state, action) {
state.name = action.payload
}
}
})
const store = configureStore({
reducer: { user: userReducer.reducer },
devTools: true
})
export const { setName } = userReducer.actions
export default store
createSlice 的作用是创建一个片段,里面的 reducer 是通过 immmer 库生成的,可以保证数据“不变”,所以在它的 reducer 中可以使用“可变”的语法,比如 state.value = xxx 这种类似 vuex 风格的语法更简单,也能达到 redux 那种效果,这依赖于 createSlice。另外 createSlice 还会自动生成 reducer 对应的 action 以及 type,使代码更少。immmer 库是基于 promise 的,直接修改属性时会拦截并返回新对象。
通过ts封装axios
import axios from "axios";
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
class MyHttp {
// 这个instance只定义了类型,在构造函数里给的值
instance: AxiosInstance
baseConfig: AxiosRequestConfig = {baseURL: '/api', timeout: 5000}
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(Object.assign(this.baseConfig, config))
this.instance.interceptors.request.use((config: any) => {
// const token = localStorage.getItem("token") as string
if(token) {
config.headers!.Authorization = token;
}
return config
}, (err:any) => {
return Promise.reject(err)
})
}
public request(config: AxiosRequestConfig): Promise<AxiosResponse> {
return this.instance.request(config)
}
public get<T=any> (url: string, config?: AxiosRequestConfig): Promise<AxiosResponse> {
return this.instance.get(url, config)
}
public post<T=any>(url: string, data?: object, config?: AxiosRequestConfig): Promise<AxiosResponse> {
return this.instance.post(url, data, config)
}
}
const http = new MyHttp({});
export default http;
全屏相关
- js包的名字:be-full,方法有beFull、isFull、exitFull
- 全屏监听事件:resize、blur+focus(监听tab切屏)、visibilitychange(监听浏览器标签页的切换)、contextmenu。
什么是宏任务和微任务
- 宏任务包括:setTimeout setInterval Ajax DOM事件
- 微任务:Promise async/await
- 微任务比宏任务的执行时间要早
同步和异步
JS是单线程执行的语言,在同一个时间只能做一件事情。这就导致后面的任务需要等到前面的任务完成才能执行,如果前面的任务很耗时就会造成后面的任务一直等待。为了解决这个问题JS中出现了同步任务和异步任务。
同步任务:
在主线程上排队执行的任务只有前一个任务执行完毕,才能执行后一个任务,形成一个执行栈。
异步任务:
不进入主线程,而是进入任务队列,当主线程中的任务执行完毕,就从任务队列中取出任务放进主线程中来进行执行。由于主线程不断重复的获得任务、执行任务、再获取再执行,所以者种机制被叫做事件循环(Event Loop)
我们都知道 Js 是单线程的,但是一些高耗时操作带来了进程阻塞的问题。为了解决这个问题,Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)
在异步模式下,创建异步任务主要分为宏任务与微任务两种。ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。
宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。
宏任务(Macrotask)大概如下:setTimeout setInterval MessageChannel I/O
setImmediate(Node环境) script(整体代码块)
微任务(Microtask)大概如下:MutationObserver(浏览器环境) promise.[ then/catch/finally ]
事件队列 process.nextTick(Node环境)
组件挂载时
当组件实例被创建并插入DOM时,其生命周期调用顺序如下:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
组件更新时
当组件的props或state发生变化时会触发更新。组件更新的生命周期调用顺序如下:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
组件卸载时
当组件从DOM中移除时会调用如下方法
错误处理
当渲染过程,生命周期或子组件的构造函数中抛出错误时,会调用如下方法:
- static getDerivedStateFromProps()
- componentDidCatch()
new的过程发生了什么?
1、new先创建了一个空对象
var news = new Object()
2、我们将这个空对象的__proto__属性指向whatnew构造函数的prototype属性,使两者都有共同的原型对象whatnew.prototype,因此空对象可以访问whatnew函数
new.__proto__ = whatnew.prototype
3、修改whatnew的this到object上面,call/apply,并把参数传入
4、最后把object返回给mynew就可以了
微前端的实现方案
什么是微服务?将一个单体应用,按照一定的规则拆分为一组服务。这些服务,各自拥有自己的仓库,可以独立开发、独立部署,有独立的边界,可以由不同的团队来管理,甚至可以使用不同的编程语言来编写。但对前端来说,仍然是一个完整的服务。以前是按照组件拆分,现在是按服务拆分。
1. 路由分发式微前端, 比如最常用的方案是通过 HTTP 服务的反向代理来实现。nginx--反向代理,不同页面的请求就可以分发到不同的服务器上。
2. iframe
3. single-spa 在 single-spa 方案中,应用被分为两类:基座应用和子应用。single-spa 会在基座应用中维护一个路由注册表,每个路由对应一个子应用。基座应用启动以后,当我们切换路由时,如果是一个新的子应用,会动态获取子应用的 js 脚本,然后执行脚本并渲染出相应的页面;如果是一个已经访问过的子应用,那么就会从缓存中获取已经缓存的子应用,激活子应用并渲染出对应的页面。
4. qiankun 是在 single-spa 的基础上做了二次开发,在框架层面解决了使用 single-spa 时需要开发人员自己编写子应用加载、通信、隔离等逻辑的问题,是一种比 single-spa 更优秀的微前端方案。
5. webpack5 以及 Web Component
vue中父组件如何监听到子组件的生命周期
1. // Child.vue
mounted() {
this.$emit("mounted");
}
2. // Parent.vue
<Child @hook:mounted="doSomething" ></Child>