Vue全家桶+React全家桶面试题及部分原理整理
Vue
v-if和v-show的区别?
v-if表达式为true显示该元素,为false隐藏该元素(直接删除)
v-show根据表达式的真假来显示与隐藏元素,会在标签加入display属性
为何v-for中要用key
vue中列表循环需加:key=“唯一标识” 唯一标识可以是item里面id index等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好的区别各个组件,key的作用主要是为了高效的更新虚拟DOM
注意:v-for和v-if不能同时使用
父子组件之间的通讯
父传子组件标签中,子组件通过props接收
子组件通过$emit()传送事件,父组件需要通过v-on去监听自定义事件
vue生命周期
------------------beforeCreate初始化之前状态(el,data,msg还没有初始化)
el:undefined
data:undefined
msg:undefined
------------------created初始化之后状态(el还没有挂载,data中已经初始化有了数据)
el:undefined
data:[object Object]
msg:hello vue
------------------beforeMount挂载之前状态(el已经挂载,但还没有渲染)
el:
{{msg}}
data:[object Object]
msg:hello vue
------------------mounted挂载之后状态(el挂载并渲染)
el:
hello vue
data:[object Object]
msg:hello vue
------------------beforeUpdate更新前
------------------updated更新后
------------------beforeDestroy销毁前的状态
------------------destroyed销毁后的状态
父子组件生命周期调用顺序
先父组件created,然后子组件created,之后子组件mounted,再父组件mounted
(创建是从外到内,渲染是从内到外,子组件渲染完再父组件)
更新阶段,先父组件update,然后子组件update,然后子组件updated,再父组件updated
何时需要使用beforeDestory
解绑自定义事件event.$off
清除自定义的DOM事件,如window scroll等
Vuex中action和mutation有何区别
action中处理异步,mutation不可以
mutation做原子操作
action可以整合多个mutation
Vue3升级内容
全部用ts重写(响应式、dom、模板编译等)
性能提升,代码量减少,会调整部分API
Proxy实现响应式(基本使用)
Proxy能规避Object.defineProperty的问题,但Proxy无法兼容所有浏览器,无法polyfill
Vue高级特性
自定义v-model
//---------------子组件传值
//绑定 input 事件,$emit 触发父组件的 input 事件
<input type="text" @input="$emit('input', $event.target.value)">
//父组件监听 input 事件,然后将事件携带的 input 输入的值传入到 data 状态中
<my-children @input="value = $event"></my-children>
export default {
data () {
return {
value: ''
}
}
}
//-------------父组件传值
//将value值传递给子组件,子组件再绑定 value值到 input的value属性上
<my-children :value="value" @input="value = $event"></my-children>
//子组件中绑定 input 的 value 属性
<input type="text" @input="$emit('input', $event.target.value)" :value="value">
//props设定value值
export default {
name: 'myChildren',
props: ['value']
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$nextTick
Vue是异步渲染,data改变之后,DOM不会立即渲染,$nextTick会在DOM渲染之后被触发,以获取最新DOM节点
页面渲染时会将data的修改做整合,多次data修改只会渲染一次
<ul ref="uls">
<li>***</li>
</ul>
this.$nextTick(()=>{
//获取DOM元素
const ulElem =this.$refs.uls
console.log(ulElem.childNodes.length)
})
1
2
3
4
5
6
7
8
9
slot
<div id="app">
<my_cpn>
<button>按钮</button>
<span slot="btn">具名插槽使用</span>
<my_cpn>
<template v-slot="get">
<span v-for="item in get.set_data">{{item}}</span>
</template>
</my_cpn>
</my_cpn>
</div>
<template id="p">
<div>
<slot><p>匿名slot插件默认内容</p></slot>
<slot name="btn"><button>按钮</button></slot>//具名插槽
<slot :set_data="str">//作用域插槽使用
<ul>
<li v-for="item in str">{{item}}</li>
</ul>
</slot>
</div>
</template>
const vm = new Vue({
el:"#app",
data:{},
components:{
my_cpn:{
template:"#p",
data(){
return {
str:["一","二","三","四"]
}
}
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
动态异步组件
:is="component-name"用法,需要根据数据,动态渲染的场景。即组件类型不确定
<component :is="NextTickName"/>//使用动态组件
<FormDemo v-if="showFormDemo"/>//异步组件
<button @click="showFormDemo = true">show form demo </button>
import NextTick from "./NextTick"//导入外部组件
export default{
components:{
NextTick,
FormDemo:() => import("../BaseUse/FormDemo")//异步加载组件
},
data(){
return {
name:"home",
NextTickName:"NextTickName"
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
keep-alive
缓存组件,频繁切换,不需要重复渲染(被包裹的组件不会被销毁)
mixin
多个组件有相同的逻辑,抽离出来
mixin并不是完美的解决方案,会有一些问题(
变量来源不明确,不利于阅读,多mixin可能造成命名冲突,mixin和组件可能出现多对多的关系,复杂度较高
),vue3提出的composition API旨在解决这些问题
<div>
<p>{{name}}{{age}}{{sex}}</p>
</div>
import myMixin from "./mixin"
export default {
mixins:[myMixin],//可以添加多个,会自动合并起来
data(){
return {
name:"张三",
age:18
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Vue部分原理(≠源码)
组件化(MVVM,数据驱动视图)
MVVM 即模型-视图-视图模型。【模型(M)】指的是后端传递的数据。【视图(V)】指的是所看到的页面。【视图模型(VM)】mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定
响应式(数据驱动视图第一步)
核心API:Object.defineProperty(Vue3.0之前)
//基本使用
const data ={}
const name="zhangsan"
Object.defineProperty(data,"name",{
get:function(){//get返回内容
console.log("get")
return name
},
set:function(newVal){//set赋值内容
console.log("set")
name=newVal
}
})
//如果监听的类型是对象,需要判断进行深度监听
//如果监听的类型是数组,不污染全局的数组原型情况下,重新定义数组原型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Object.defineProperty的缺点:深度监听,需要递归到底,一次性计算量大
无法监听新增属性/删除属性(需要Vue.set Vue.delete两个API来使用)
Vue3.0使用Proxy取代,但Proxy有兼容性问题,且无法polyfill,所以Vue2.x还会存在一段时间
vdom(Virtual DOM)和diff(虚拟DOM和diff算法,面试中的热门问题)
vdom----用js模拟DOM结构,计算出最小的变更,操作DOM
vue是参考snabbdom实现的vdom和diff
----------diff算法
diff即对比,是一个广泛的概念,如linux diff命令,git diff等
diff算法过程:patch(elem,vnode)和patch(vnode,newVnode)
patchVnode和addVnodes和removeVnodes
updateChidren(key的重要性)
两课树做diff
模板编译
前置知识点:with语法
改变{}内自由变量的查找规则,当做obj属性来查找,如果找不到匹配的obj属性,就会报错
with要慎用,它打破了作用域规则,易读性变差
模板它不是html,有指令,插值,js表达式,能实现判断,循环
模板编译为render函数,执行render函数返回vnode
基于vnode再执行patch和diff
vue组件中使用render代替template
渲染过程
①初次渲染过程
解析模板为render函数
触发响应式,监听data属性 getter setter
执行render函数,生成vnode,patch(elem,vnode)
②更新过程
修改data,触发setter(此前在getter中已被监听)
重新执行render函数,生成newVnode
path(vnode,newVnode)
③异步渲染
通过$nextTick方法
汇总data的修改,一次性更新视图,减少DOM操作次数,提高性能
前端路由
①hash
hash的特点:hash变化不会触发网页跳转,即浏览器的前进、后退
hash变化不会刷新页面,SPA必需的特点
hash永远不会提交到server端
②H5 history
用url规范的路由,但跳转时不刷新页面
H5 history需要后端支持
to B的系统推荐用hash,简单易用,对url规范不敏感
to C的系统,可以考虑选择H5 history,但需要服务端支持
React
React vs Vue
React和Vue一样重要(特别是大厂面试),力求两者都学会
React和Vue有很多相通之处,而且正在趋于一致
React比Vue学习成本高,尤其对于初学者
React的event
①event是SyntheticEvent,模拟出来DOM事件所有能力
②event.nativeEvent是原生事件对象
③所有的事件,都被挂载到document上
④和DOM事件不一样,和Vue事件也不一样
受控组件和非受控组件
例如等元素都要绑定一个change事件,当表单的状态发生变化,就会触发onChange事件,更新组件的state。这种组件在React中被称为受控组件,在受控组件中,组件渲染出的状态与他的value或checked属性相对应,react通过这种方式消除了组件的局部状态,使整个状态可控。react官方同样推荐使用受控表单组件
如果一个表单组件没有value props(单选和复选按钮对应的是checked props)时,就可以称为非受控组件.
在非受控组件中,我们可以使用一个ref来从DOM获得表单值。而不是为每个状态更新编写一个事件处理程序。
setState(同步还是异步)
setState之前不可变值,可能是异步更新,可能会被合并(传入对象的情况下,多个setState只执行一次,传入函数,不会被合并)
同步还是异步?
setState直接使用是异步的,想获取state中最新的值,在setState加入回调
setTimeout中setState是同步的,自定义的DOM事件中也是同步的
React组件生命周期
1.第一次初始化渲染显示:ReactDOM.render()
construction() 创建对象初始化state
componentWillMount() 组件渲染之前
render() 渲染组件
componentDidMount() 组件渲染之后
2.每次更新this.setState()
componentWillUpdata() 将要更新之前
render() 更新(重新渲染)
componentDidUpdata()已经更新之后
3.移除组件ReactDOM.unmountComponentAtNode(containerDom dom容器)
componentWillUnmount()组件要被移除回调
React高级特性
函数组件
非受控组件
使用场景
必须手动操作DOM元素,setState实现不了
文件上传
showInput = () => {
const input = this.refs.content//之前版本获取ref的标签
console.log(input.value);
console.log(this.input.value);//新方法
}
render(){
<input type="text" ref={input => this.input = input}//ref标识组件内的元素
<button onClick={this.showInput}>获取input值</button>
}
1
2
3
4
5
6
7
8
9
Portals
组件默认会按照既定层次嵌套渲染。如何让组件渲染到父组件以外?
Portals使用场景
overflow:hidden 父组件z-index值太小 fixed需要放在body第一层级
基本使用:
render(){
//使用Portals渲染到body上
return ReactDOM.createPortal(
<div className="modal">{this.props.children}</div>,
document.body
)
}
1
2
3
4
5
6
7
context
公共信息(语言、主题)如何传递给每个组件?用props太繁琐,用redux小题大做
React.createContext:创建一个上下文的容器(组件), defaultValue可以设置共享的默认数据
const {Provider, Consumer} = React.createContext(defaultValue)
1
Provider(生产者): 和他的名字一样。用于生产共享数据的地方。生产什么呢? 那就看value定义的是什么了。value:放置共享的数据。
<Provider value={/*共享的数据*/}>
/*里面可以渲染对应的内容*/
</Provider>
1
2
3
Consumer(消费者):这个可以理解为消费者。 他是专门消费供应商(Provider 上面提到的)产生数据。Consumer需要嵌套在生产者下面。才能通过回调的方式拿到共享的数据源。当然也可以单独使用,那就只能消费到上文提到的defaultValue
<Consumer>
{value => /*根据上下文 进行渲染相应内容*/}
</Consumer>
1
2
3
异步组件
Vue中使用import() React中使用React.lazy React.Suspense
从 React 中引入 lazy 方法和 Suspense 组件,然后用 lazy 方法处理我们的组件,lazy 会返回一个新的React 组件,我们可以直接在 Suspense 标签内使用,这样组件就会在匹配的时候才加载。
lazy 接受一个函数作为参数,函数内部使用 import() 方法异步加载组件,加载的结果返回。
Suspense 组件的 fallback 属性是必填属性,它接受一个组件,在内部的异步组件还未加载完成时显示,所以我们通常传递一个 Loading 组件给它,如果没有传递的话,就会报错。
基本使用:
import React, { lazy, Suspense } from 'react';
const Index = lazy(() => import('components/Index'));
const List = lazy(() => import('components/List'));
class App extends React.Component {
render() {
return <div>
<Suspense fallback={Loading}>//fallback 属性是必填属性,它接受一个组件
<Index></Index>
<List></List>
</Suspense>
</div>
}
}
function Loading() {
return <div>
Loading...
</div>
}
export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
性能优化
shouldComponentUpdate(简称SCU)
_.isEqual方法做对象或者数组的深度比较
SCU默认返回true,即React默认重新渲染所有子组件,必须配合“不可变值”一起使用,可先不用SCU,有性能问题时再考虑使用
PureComponent和React.memo
PureComponent,SCU中实现了浅比较
memo,函数组件中的PuceComponent,浅比较已使用大部分情况(尽量不要做深度比较)
不可变值immutable.js
彻底拥抱“不可变值”,基于共享数据(不是深拷贝),速度好,按需使用
高阶组件HOC
基本用法
Render Props
Redux使用
和Vuex作用相同,但比Vuex学习成本高,不可变值,纯函数
基本概念
store state
action
reducer
import { createStore } from 'redux';
/**
* 这是一个 reducer,形式为 (state, action) => state 的纯函数。
* 描述了 action 如何把 state 转变成下一个 state。
*
* state 的形式取决于你,可以是基本类型、数组、对象、
* 甚至是 Immutable.js 生成的数据结构。惟一的要点是
* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
*
* 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);
// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
console.log(store.getState())
);
// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
单项数据流
react-redux
异步action
redux中间件
React-router使用
路由模式(hash、H5 history),同vue-router,路由配置(动态路由、懒加载),同vue-router
React部分原理(≠源码)
函数式编程
一种编程范式,概念比较多,纯函数,不可变值
函数式编程:是一种设计思想,尽量用函数组合来进行编程,先声明函数,然后调用函数的每一步都有返回值,将具体的每一步逻辑运算抽象,封装在函数中。再将函数组合来编写程序。
vdom和diff
JSX本质
合并事件
batchUpdate机制
setState
batchUpdate机制
transaction事务机制
组件渲染过程
内容来源bilibili,链接视频