# React

# 1.diff算法?及虚拟dom?

diff算法本质就是找出虚拟dom和真实dom的差异

# 什么是调和(协调)=>reconciliation?

将虚拟dom转成真实dom的过程叫做调和。diff算法就是调和的具体表现

# 2.fiber是什么?

调和算法=>用来解决大量同步计算阻塞浏览器UI渲染的问题=>fiber本身可以看作一个js对象,fiber是链表结构(环形链表) 是一种基于浏览器的单线程调度算法。主要用来做异步diff(虚拟dom渐进式渲染),类似链表=》将原来的递归diff变成了遍历diff

# fiber结构

const fiber = {
    stateNode, // 节点实例
    child,     // 子节点 => 指向当前节点的第一个子节点
    sibling,   // 兄弟节点 => 指向当前节点下一个兄弟节点
    return     // 父节点 => 指向当前节点的父节点
}
1
2
3
4
5
6

# scheduler=>调度器实现任务分配=>6种优先级

  • synchronous => 同Stack Reconciler 同步执行
  • task => next tick之前执行
  • animation => 下一帧之前执行
  • high => 不久的将来立即执行
  • low => 稍微延迟执行也可以
  • offscreen => 下一次render时或者scroll时才执行

# fiber影响

fiber异步diff,任务的更新过程可能会被打断,所以在更新过程中,render及其之前的生命周期函数可能会被调用多次 以下生命周期中不应出现副作用=>是unsafe的原因,也是钩子废弃原因

  • shouldComponentUpdate
  • componentWillMount(UNSAFE_componentWillMount)
  • componentWillReceiveProps(UNSAFE_componentWillReceiveProps)
  • componentWillUpdate(UNSAFE_componentWillUpdate)

# react内部分层

virtual dom 层 => 负责页面样式 reconciler 层 => 负责调用组件生命周期方法,进行diff计算 renderer 层 => 根据不同平台,渲染相同的页面 => 如reactDom和reactNative

# fiber原理

  • 在react16之前(使用stack reconciler(栈协调器)),当react要更新dom时,从diff到更新dom,整个过程一气呵成,不能被打断。 (当组件层级过深,用户点击了页面某个按钮,可能会因为批量更新dom还未完成,导致按钮无法响应的问题)
  • react16之后使用的是fiber reconciler(纤维协调器),将递归中无法中断的更新重构为异步可中断过程,这样就能更好的控制组件的渲染。 可以分段执行(链表结构)

# 双缓存是什么?

react维护了两个fiber树

  • current Fiber 用于渲染页面
  • workingProgress Fiber 用在内存构建中,构建完成后直接替换current Fiber树

# diff算法如何从O(n3)优化到O(n)的=》可以理解为O(n3)是传统树递归diff的时间复杂度=》基于哪两个假设

  1. 两个不同类型的元素会产生不同的树
  2. 同一层级的一组节点,他们可以通过唯一的id进行区分

# 如何从O(n^3)转成O(n)=》基于两大假设通过三大策略来优化

React用三大策略将O(n^3)转成O(n)复杂度

  1. tree diff 只比较同一层的dom节点(忽略dom节点的跨层级移动,如果发生会直接删除旧节点使用新节点)=》只需要对树进行一次遍历就可以完成整棵树的比较
  2. component diff (同一类型的组件会继续比较,不同类型的直接替换) 同一类型的两个组件按原来层级比较的方式继续比较即可(shouldComponentUpdate可以判断是否需要计算) 不同类型组件,会把将要被改变组件判断为脏组件(dirtyComponent),从而替换整个组件的所有节点
  3. element diff 同一层级的一组子节点,通过唯一ID区分即key(diff提供三种节点操作:删除,移动,插入)

# 3.setState是同步还是异步?(私下简单记忆:同步逻辑中就异步,异步逻辑中就同步)

在生命周期函数和合成事件中是异步的,在原生事件和setTimeout中是同步的

  • 在react事件中是异步
  • 在setTimeout或者自定义dom事件中都是同步

# 为什么不直接都同步?

做成异步是为了减少频繁setState带来的性能损耗=》setState会重新渲染页面

# 什么是合成事件?

  • 提供统一API去抹平浏览器差异,所有事件都绑定在React根元素(Root Element)进行事件委托,不是绑在元素上
  • 比如onChange事件就是合成事件(在react中onChange也有OnInput的功能,多个原生js事件的合成事件)

# 为什么不能直接更新状态?

不能触发UI重新渲染=》setState里面有做处理

# 4.UI组件与容器组件?

  • UI组件: 只负责UI,没有业务逻辑,没有状态,数据由props提供,不使用redux的api
  • 容器组件:负责管理数据和业务逻辑,不负责UI,有内部状态,使用redux的api =》容器组件用来处理获取数据,订阅redux存储等的组件,里面没有html

# 5.react学习

不是完整的MVC框架,只能算是view层 css样式名要用驼峰写法 fontSize:17px=》类名用className避免与类冲突 render函数内注释{//test} 在class中定义的是对象属性,需要new才可以访问,在外部定义的是类属性可以直接访问(函数式组件也是在外部定义)。在class中给属性加static也会变成类属性可以直接访问 类组件通过this.props获取值,函数组件通过function(props)获得 不能在render直接调用setState,setState会触发render,造成循环调用 路由开启exact可以精确匹配,否则是模糊匹配=》嵌套路由 父路由不能设置精确匹配=>同级路由(非嵌套)前缀相同需要开启exact 生命周期will在进入前调用,did是进入后调用 在componentDidMount发起异步请求

# 6.*生命周期(只针对类,函数式组件无生命周期)=》括号里都是react16以后的钩子

# 初始化(挂载期):一个组件实例初次被创建的过程

  • constructor:(先super(props))初始化state (static getDerivedStateFromProps替代componentWillMount,旧的即将废弃)
  • componentWillMount:render前最后一次修改状态的机会,可以用来做状态计算或处理,只走一次,在渲染前调用=>过时
  • render:只能访问this.props和this.stat,不允许修改状态和dom
  • componentDidMount(CDM):成功render并渲染完成真实dom之后会触发(第一次渲染后调用),可以修改dom,只走一次,可以用来放setTimeout,setInterval或者ajax请求

# 源码过程

constructor(构造函数,初始化状态值)->getInitialState(设置状态机)->getDefaultProps(获取默认的props)->UNSAFE_componentWillMount(首次渲染前执行) ->render(渲染组件)->componentDidMount(render渲染之后执行的操作)

# 运行中(更新期):组件在创建后再次渲染的过程

(static getDerivedStateFromProps 替代componentWillReceiveProps, 旧的即将废弃)

  • componentWillReceiveProps:父组件修改属性触发,会最先获得父组件传来的属性=>提供了最新的props,此时的this.props是老的=》过时 =》在组件收到一个新props(更新后)调用,初始化render时不调用
  • shouldComponentUpdate(SCU):返回boolean值,返回false阻止render调用,在组件接受到新props或state时调用(初始化或者forceUpdate时不调用) =》通俗来说状态不变不更新false,状态改变要更新true=》提供了最新的props和最新的state,此时的this.state是老状态 (getSnapshotBeforeUpdate替代componentWillUpdate, 旧的即将废弃)
  • componentWillUpdate:不能修改属性和状态,在组件收到新props或者state但还没render时调用,初始化不调用=>过时
  • render:只能访问this.props和this.stat,不能修改状态和dom
  • componentDidUpdate(CDU):组件更新完成后立即调用,初始化不调用可以修改dom=》该方法还提供了老的props和老的state=》可以通过比较新老数据异同来决定是否触发操作

# 源码过程

UNSAFE_componentsWillReceiveProps(当父组件更新子组件state时,该方法会被调用) ->shouldComponentUpdate(该方法决定组件state或props的改变是否需要重新渲染组件) ->UNSAFE_componentWillUpdate(在组件接收新的state或者props时,重新渲染之前调用该方法) ->componentDidUpdate(在组件重新渲染后调用该方法)

# 销毁(卸载期):组件在使用完被销毁的过程

  • componentWillUnmount(用来将组件从DOM树删除):组件从dom移除时立即调用=》在删除组件之前进行清理操作,清除计时器或事件监听

# UNSAFE

unsafe的生命周期都属于fiber的第一阶段,这个阶段优先级都比较低,有可能被打断,将来可能被执行多次,所以不安全

# 新生命周期=>目的就是解决老生命周期三个unsafe生命周期的问题

getDerivedStateFromProps()会在初始化挂载和更新(自身状态更新或父状态更新)时触发,在dom渲染前调用=》可以获取最新的props和state,是类属性,前面要加static,没有this,需要返回值没有就返回null =>在初始化可以代替componentWillMount,在父传子的时候可以代替componentWillReceiveProps getSnapshotBeforeUpdate代替componentWillUpdate(不能同时使用),在update发生时,在render之后dom渲染前返回值,作为componentDidUpdate的第三个参数 =》可以用来做滚动条定位,页面刷新仍然保持原来的滚动条

# 错误阶段

(static getDerivedStateFromError) (componentDidCatch)

# 7.react脚手架

  • create-react-app(npx create-react-app 项目名)
  • umijs

# 区别

  • create-react-app不包含路由,但是在webpack打包层的优化做了比较多
  • 而umi是以路由为基础的(实现了完整的生命周期),但兼容性不如create-react-app(不支持ie8以下,react16.8以下也不支持)
  • umi创建的项目结构更复杂,原来的会更简单些=>umi开箱即用

# react最流行的css in js库

emotion,styled-components

# 8.shouldComponentUpdate?

  • render()函数决定组件该渲染什么,shouldComponentUpdate决定组件什么时候不需要渲染
  • react的component提供了shouldComponentUpdate的默认实现,会返回一个true代表每次更新都要调用所有生命周期函数
  • 所以我们通过自定义shouldComponentUpdate方法,使它在需要更新的时候才返回true,这样达到一个性能优化的效果
  • 只要组件的completed和text没变,就可以让shouldComponentUpdate返回false
  • react-redux默认实现对比props方面采用了浅比较(不采用深比较是因为性能消耗的问题=》可以通过immutable这个库来实现深比较)

# 结论

可以在shouldComponentUpdate使用浅比较来比较prop,深比较通过Immutable这个库来实现

# 9.react-router-dom和react-router区别?

react-router-dom是在react-router基础上开发的,额外提供了BrowserRouter,HashRouter,Link,NavLink组件用来路由跳转

  • (browserRouter,hashRouter替代了router,link和NavLink作用类似a标签)
  • browserRouter是history模式,hashRouter是hash模式
  • Redirect用来强制路由重定向
  • Switch用来组合路由

# react-dom

用来操作dom

# react-dom常用api

  • createPortal()=>传送门,该节点在dom组件层次结构外
  • render()用来将react元素渲染到container提供的dom中

# 10.setState时发生了什么?

(每次setState,组件会重新渲染)

  • 在函数式组件中setState时,如果两次设置的state相同时,组件不会重新渲染
  • 在事件中多次调用setState,会批量进行渲染
  • 在事件外多次调用setState,不会重新渲染
  • react18后同一函数多次调用setState都会批量渲染

# setState之后发生什么?

  1. 传入的参数和组件当前状态合并,触发调和
  2. 根据新状态构建react元素树
  3. 得到元素树后会重新计算新老树差异,然后按需更新=》不用全部重新渲染

# 11.怎么理解在react中,一切都是属性?

组件会将UI分成多个独立的部分,每个组件彼此独立

# 12.useState为什么要使用数组而不是对象?

使用对象根据key解构不如使用数组解构方便,数组通过索引解构

# 13.常见hooks功能(react 16.8引入了hooks)

hook只能在顶层使用,不能在循环,条件或嵌套函数中使用 1.useState声明变量:

class中this.state = {count: 0}
hook中const [count, setCount] = useState(0)
1
2

2.useEffect用来处理副作用,useLayoutEffect同步处理副作用=>不能在useEffect中使用useState =>函数会在组件渲染后执行(网络请求,dom操作都属于副作用)

useEffect(()  =>  {
// Async Action
}, [dependencies,为空数组表示不依赖,数组有值该值改变会再次触发useEffect])
1
2
3

如果传空数组只执行一次,可以通过return一个函数来对定时器进行销毁

# useEffect和useLayoutEffect区别?

调用时机不同,useLayoutEffect和原来componentDidMount和ComponentDidUpdate一致,在dom更新后马上调用,会阻塞页面渲染,useEffect是等整个页面渲染完才调用,官方建议用useEffect =》实际开发中,如果useEffect操作dom导致页面抖动,可以把操作dom部分放到useLayoutEffect中,这样就只会触发一次重绘和重排 =》useLayoutEffect先于useEffect执行 3.useCallback

useCallback(() => {
}, [dependencies,为空数组在第一次创建后就被缓存后续不改变,不传入第二个参数每次都会重新调用])
1
2

用来优化性能,能够对函数进行缓存(不执行),只有依赖的数据发生变化,才会重新缓存新的回调函数 4.useMemo=》不推荐所有组件都进行缓存,大量组件初始化时被缓存,会导致过多的内存消耗,影响初始化渲染速度=>不能进行有副作用的操作,如网络请求 用来优化性能,依赖不变的情况下,会返回相同的引用,避免子组件重复渲染,可以完全替代useCallback,区别是useMemo会直接返回函数结果

const memoizedValue = useMemo(() => 计算函数(a, b), [a, b依赖]); 返回一个缓存值
1

5.useRef useRef(0)=》能设置初始值 这个方法用来获取组件实例或dom元素 =》创建ref可以通过createRef和useRef

const refContainer = useRef(initialValue);
1

6.useContext 组件间共享状态,子组件(消费者)可以通过useContext()获取父组件的属性,生产者还是通过React.createContext去创建context对象

const value = useContext(MyContext);
1

7.useReducer=》先有redux后有useReducer=》不支持异步,异步使用redux=》作用是在复杂场景下替换useState

const [state, dispatch] = useReducer(reducer函数, 初始值); 
1

reducer函数可以获得之前的state和dispatch传递的参数reducer(prevState, 传递的参数)

# useState和useReducer区别?

  • useState如果初始值是函数返回值要通过回调函数的方式设置,直接设置会导致没有重新赋值但获取初始值的函数重复执行=>通过getCount获取初始值,重复执行getCount
  • useReducer初始值如果通过函数返回,当组件重新渲染时会重复执行这个函数
  • useState修改状态,同个useState声明的状态会被覆盖,而useReducer会按顺序执行=>setCount(2)两次一个是2,一个是4

# useMemo和useCallback区别?

useCallback第一次不会执行函数,只是缓存函数,useMemo会执行并把执行结果返回

# 自定义hooks钩子=》作用是逻辑复用,允许没有返回值

只能在函数组件和其他hooks中调用,同时其他hook也只能在自定义hook内部调用(如果用普通函数抽离逻辑去处理没办法使用hooks) 自定义钩子必须以use开头,函数内部可以调用其他hook

# 实例=》可以自定义逻辑性和功能性的hooks=>两组件使用相同的hook不会共享状态

// 逻辑性hooks 获取列表后通过useList()获取返回值
function useList() {
    const [list,setList] = setState([])
    useEffect(()=> {
        axios({}).then(res=> {
            setList(res)
        })
    }, [])
    return {list}
}
function app() {
    const {list} = useList()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 功能性hooks
const useWidth = () => {
    const [width, setWidth] = useState(window.innerWidth)
    const onResize = () => {
        setWidth(window.innerWidth)
    }
    useEffect(() => {
        window.addEventListener('resize', onResize)
        return () => {
            window.removeEventListener('resize', onResize)
        }
    }, [])
    return {width}
}
// 页面上通过const {width} = useWidth()拿到width 然后在html上使用<button width={width}/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# *useState为什么要使用数组,不用对象?

降低使用的复杂度,因为使用对象的话使用多次就需要通过取别名的方式

# 为什么使用hooks?(官方更推荐用hooks写法)

  • 高阶组件为了复用,使得代码层级更复杂
  • 生命周期复杂
  • 函数式组件无状态,如果需要状态就要改成类组件,不方便=》主要原因
  • *不用操心this指向问题

# hooks原理=》基于fiber,在fiber节点上放一个链表

对于effect副作用钩子,会绑定在workInProgress.updateQueue上,等到commit阶段,dom树会构建完成,再去执行每个effect副作用钩子 核心:hooks主要通过闭包来保存状态,使用链表保存hooks 初始化:renderWithHooks执行函数组件 mount阶段:

  • 每个hooks都会通过mountWorkInProgressHook生成一个hook对象,每个hooks以链表的形式保存(.next),赋值给workInProgress(Fiber节点)的memoizedState属性
  • 每个hook对象包含memoizedState(useState保存state|useEffect保存effect对象|useMemo保存缓存值和deps|useRef保存ref对象), baseQueue(useState和useReducer中保存最新的更新队列),baseState(useState和useReducer一次更新产生的最新state值),queue(保存待更新队列pendingQueue,更新函数dispatch信息), next(指向下一个hooks对象) (mountState初始化useState,dispatchAction控制无状态组件更新,mountEffect初始化useEffect,mountMemo初始化useMemo,mountRef初始化useRef) update阶段:
  • 通过updateWorkInProgressHook更新hooks链表,找到对应hooks
  • updateState得到最新state
  • updateEffect更新updateQueue
  • updateMemo判断deps,获取/更新缓存
  • updateRef更新ref对象

# hook中的memoizedState和fiber中的memoizedState区别?

hook中是用来保存state值,fiber中是指向当前fiber下hook队列的首个hook,因为hook是链表结构,通过这个hook可以访问整个hook队列

# 14.hooks如何模拟componentDidMount?

使用useEffect,把第二个参数设为[] =》useEffect(callback,[])=》只在初次渲染时调用,如果不设置第二个参数每次都会调用

# useEffect模拟componentDidUpdate

  • 使用条件判断依赖是否是初始值,不是就走更新逻辑=》缺点是多个依赖项需要多次比较
  • 使用useRef设置一个初始值进行比较
const mounting = useRef(true);
   useEffect(() => {
      if (mounting.current) {
          console.log("初次")
          mounting.current = false;
          return 
      }
      console.log("DidUpdated")
});
1
2
3
4
5
6
7
8
9

# 15.hooks如何部分替代redux功能?

useReducer + useContext =》 客户端全局store

# 16.react函数式组件优点?

  • 类组件需要创建类实例性能消耗大,函数式组件不需要
  • 函数式组件更简单易理解

# 17.react在1s内点击按钮多次,怎么获得最后一次的更新状态?

setState支持两种语法

  • setState(updater, cb) => 接受函数类型,返回一个对象
this.setState((state) => { //没有 this
    return { count: state.count + 1 };   //2
});
this.setState((state) => { // 没有 this
    return { count: state.count + 1 };   //2
});
结果是+2
1
2
3
4
5
6
7
  • setState(stateChange, cb) => 接受对象类型=》会对多个setState进行批处理更新
this.setState({
    count:this.state.count+1   // 1
})
this.setState({
    count:this.state.count+1    //2
})
结果是+1
1
2
3
4
5
6
7
  • 直接通过this.state.count修改值不会触发页面更新
  • state发生变化会触发生命周期,当render(或shouldComponentUpdate返回false)之后this.state才更新

# 18.*react和vue中受控组件和不受控组件区别?

  • 受控组件一般需要初始状态和一个状态更新函数(onChange触发)
  • 非受控组件只在初始化接受外部数据,通过ref去获取值
  • 简单说受控组件由state控制,需要事件去更新,非受控组件比如在input,输入值页面就显示最新值,受控组件就不能显示
  • 组件的数据渲染是否被props完全控制,控制则为受控组件,否则为非受控组件

# 19.说说对redux了解?

redux就是react的状态管理工具=>基于js,其他框架也可以使用=>是flux架构思想的一种实现=》flux由这几部分组成(actions,dispatcher,stores,action,views)

  • flux是单向数据流
  • action中的payload时可选的,type必填,action将其转发给reducer,当reducer收到后,通过switch...case比较action中的type,对相应的内容返回新的state
  • 当redux状态更改时,连接到redux的组件将接受新的状态作为props。当组件接受到这些props时,它将进入更新阶段并重新渲染UI
  • ReactComponents=》actions=》Reducers=》store=》ReactComponents
  • 通过store存储,createStore来生成store,store.getState就能拿到当前的数据
  • action: {type: ''} type是必有的属性 表示action名称
  • store.dispatch({type: ''})=>里面是action对象,更新状态
  • reducer是个纯函数,reducer = (state,action) => {return newState} 返回的是新的state
  • store.subscribe(listener)=》设置监听函数

# 组件如何与redux连接?

  • mapStateProps:将state映射到props上,只要state发生变化,新state会重新映射props(订阅store方式)
  • mapDispatchToProps:将action creators绑定到props上,可以通过props.actions.xxx来触发
  • connect用来连接store

# 工作流

通过dispatch(action)到store,store通过reducer函数根据action进行相应的处理,然后再返回(UI)=》先subscribe订阅后dispatch触发

# 核心原理

主要是返回三个函数:subscribe订阅,dispatch触发,getState返回最新的状态

  • 内部维护了一个数组,subscribe把回调函数保存在数组中
  • 内部有老的state,dispatch会根据action在reducer中做相应处理然后更新state(新的state由reducer返回),再依次执行数组中的回调函数
  • 最后可以通过getState获取最新的state

# redux和react-redux关系

react-redux目的是在组件上层传入store,下层通过dispatch去更新store的state,与redux本身没有关系

# redux主要api

  • createStore()创建store
  • combineReducers()组合reduces
  • applyMiddleware()引入中间件
  • getState()
  • dispatch(action)
  • subscribe(listener)

# react-redux主要api

  • provider
  • connect组件包裹,被包裹的组件获得redux的state和action,返回一个高阶组件=》通过props.xxx去获得reducer返回的值
  • useStore,useSelector,useDispatch=》要在provider包裹的组件内使用

# 三大原则

  1. state以单一对象存储在store对象中
  2. state只读(每次只返回一个新对象)
  3. 使用纯函数(reducer)执行state更新

# 纯函数

  1. 对外界没有副作用=》副作用就是调用函数后影响了原来传入的对象,不影响就没有副作用
  2. 同样的输入得到同样的输出

# 副作用

一个函数如果同样的输入得到不同的输出就可以说这个函数是包含副作用的

# 函数式编程核心概念

  • 不可变性:数据不可改变,可以通过Object.assign拷贝并返回新对象
  • 纯函数
  • 数据转换:.join()|.filter()|.map()都是返回新的数组或对象,不改变原有对象
  • 高阶函数:将函数作为参数或返回函数的函数
  • 递归+组合:将较小函数组合成更大的函数

# 异步中间件

redux-thunk=>是对象就直接向下执行(相当于没做任何处理),是函数会先执行,在函数内部去调用dispatch=>要在createStore的时候通过applyMiddleware进行配置 =>源码通过typeof action === 'function' 判断是否是函数

# react-redux

connect高阶组件(HOC=》Higher-order-component),第二个参数可以传给子组件的回调函数

# redux-saga=>和redux-thunk一样都是异步中间件,使用saga需要去学习,thunk通过promise使用更容易上手

  • 全局监听器和接收器使用generator和saga自身的一些辅助函数实现对整个流程的管控
  • 也是中间件需要通过applyMiddleware进行配置=>通过.run(watchSaga)调用监听函数
  • 通过all函数可以同时监听多个watchSaga
function *watchSaga() {
   while(true) {
       // take监听组件的action
       yield take(action)
       // fork同步执行异步函数getList
       yield fork(getList)
   }
}
1
2
3
4
5
6
7
8

# 20.react17=》最新是18(22年三月发布)

react使用jsx,jsx的script属性是text/babel,允许html和js混写

# 21.useEffect和componentDidMount差异?

useEffect会捕获到props和state

# 22.ref?

  • 旧版:组件上 ref="username" 通过this.refs.username去获取 =>严格模式不允许使用
  • 新版:声明myRef = React.createRef() 再组件上 ref={this.myRef} 通过this.myRef.current去获取

# refs作用

返回对元素的引用=》可以用来操作dom和获取组件实例

# 23.组件通信

父子通信:props=》父向子 子向父通过调用props传过来的方法,让父组件来更新

// 子组件: Child
const Child = props =>{
  const cb = msg =>{
      return ()=>{
          props.callback(msg)
      }
  }
  return (
      <button onClick={cb("京程一灯欢迎你!")}>京程一灯欢迎你</button>
  )
}
// 父组件 Parent
class Parent extends Component {
    callback(msg){
        console.log(msg)
    }
    render(){
        return <Child callback={this.callback.bind(this)}></Child>    
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

非父子:

  1. context进行跨层级通信 createContext创建上下文 useContext使用上下文
let GlobalContext = React.createContext()
然后在父组件上包装一个provide
<GlobalContext.Provider>
<aa/>
</GlobalContext.Provider value={{info: 'xx'}}>
接收者通过接收
<GlobalContext.Consumer>
{(value) => {
    return <div>111</div>
}}
</GlobalContext.Consumer>
1
2
3
4
5
6
7
8
9
10
11
  1. 发布订阅模式 subscribe进行订阅,通过publish进行发布
  2. 状态提升(中间人模式) =>子传父 再由父传给相应组件

# 24.父组件如何调用子组件方法?

  • react >=16.8 函数中使用通过useRef
  • react >=16.4 类中使用通过createRef

# 25.性能优化

# 类组件:

  • shouldComponentUpdate生命周期函数来自定义渲染逻辑
  • pureComponent(纯组件)会自动比较新旧props和state,决定shouldComponentUpdate返回true或false,从而决定要不要render =》React.component内部没有实现shouldComponentUpdate不会自动比较=》等同于React.memo都是浅比较

# 函数组件:

  • react.memo高阶函数包装组件=》HOC=》接收组件A作为参数并返回一个组件B,组件B的props没有改变,组件B就是阻止组件A重新渲染(浅比较)=》也可用于类组件 =>通过React.memo(组件)对组件进行包裹=>第二个参数可以自定义比较函数
  • useMemo原理和pureComponent一样,都是自动比较props
  • useCallBack

# useMemo和react.memo区别?

  • react.memo是hoc,useMemo是个hook
  • 使用React.memo返回的是一个组件,只要props没有改变就不会重新渲染
  • 使用useMemo当依赖项不变返回的就是缓存的值,避免重新渲染

# 什么是memoization(记忆化)?

是个过程,允许缓存函数调用的值,这样下次调用就会直接返回值,不用再进行计算

# 手写useMemo

function useMemos(nextCreate, dependencies) {
  if (hookStates[hookIndex]) {   // 说明不是第一次渲染
    let [lastMemo, lastDependencies] = hookStates[hookIndex];
    let same = dependencies.every((item, index) => item === lastDependencies[index]);
    if (same) {
      hookIndex++;
      return lastMemo;
    } else {
        const newMemo = callBack()
        hookState[hookIndex++] = [newMemo, dependencies]
    }
  } else {
        const newMemo = callBack()
        hookState[hookIndex++] = [newMemo, dependencies]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 手写useCallback

let hookStates: any = [];
let hookIndex: number = 0;
function useCallbacks(callback: any, dependencies: any) {
  if (hookStates[hookIndex]) {   // 说明不是第一次渲染
    let [lastCallback, lastDependencies] = hookStates[hookIndex];
    let same = dependencies.every((item: any, index: any) => item === lastDependencies[index]);
    if (same) {
        hookIndex++;
        return lastCallback;
        }
    }
    // 第一次渲染 或者 不是第一次但是依赖项相同,都返回新的
    hookStates[hookIndex++] = [callback, dependencies];
    return callback
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 26.react如何区分类组件和函数组件?

通过原型链判断是否有React.Component即可=》Component.prototype.isReactComponent =》类组件的原型上有isReactComponent属性

# 27.jsx原理?

通过babel编译成js对象,然后通过render函数映射成dom节点插入页面=>浏览器不能直接读取jsx

# 自定义组件名为什么要大写?

区分是组件还是html(dom)元素,大写是组件,小写是dom元素

# 是否可以不使用jsx?

可以,但是不使用jsx比如创建一个div就需要通过react.createElement来创建,比较麻烦

// 使用jsx
class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}
// 不使用jsx
class Hello extends React.Component {
  render() {
    return React.createElement('div', null, `Hello ${this.props.toWhat}`);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 28.props透传?

高层级中的props传递到更深层级的组件就是props透传

# 29.空标签的作用?

  • <>表示空标签,react.Fragment()可以用来将子列表分组,并且不在dom中增加额外节点
  • <></> 是 <React.Fragment/> 的语法糖。
  • 空标签不能接受键值或属性,需要属性通过这种方式<React.Fragment key={item.id}/>

# 30.属性和状态的区别?

属性是props,状态是state。都会触发render更新=》没有state的是无状态组件

# 区别

  • 属性可以从父组件获取和修改,状态不能
  • 属性可以修改子组件值,状态不可以 =》状态只能被组件自身修改不能被外部访问和修改,属性是外部传进来的参数,内部无法修改,只能由外部来修改

# 31.插槽?

在定义的插槽内部通过this.props.children获取要填充的内容

# 32.声明式导航和编程式导航区别?

  • 声明式导航:aaa =>
  • 编程式:location.href = "/index.html"=>this.props.history.push('index.html')
  • 动态路由/a/:id =>然后通过props.match.params.id可以获取到id
  • 路由传参:this.props.history.push({pathname:'/user',query: {a: 1}}) =>this.props.location.query.a获取
  • browserRouter路径没有#,会真正向后端发送请求要页面,没有就404。另一种hashRouter带有#
  • withRouter可以把组件包起来,传递history,match,location这些属性=》高阶组件HOC

# 33.反向代理?

httpProxyMiddleware可以进行代理,使用接近webpack的devServer=》利用后端之间无跨域来实现

# 34.说说对cssModule?

  • 可以将css模块化,通过修改css文件名称,如tab.module.css即可=》生成的样式名会自动拼接随机串防止重复
  • :global(.active样式名)即可实现全局样式定义,即使在module文件中也全局生效

# 35.immutable作用?

  • 深拷贝=》新对象上操作不会影响到原对象的数据,每次修改一个immutable对象都会创建一个新的不可变对象
  • 转普通js对象=》immuObj.toJS()
  • fromJS()会自动转成immutable对象

# 常用api

set(),setIn(),get(),updateIn()

# immer和immutable区别?

  • immutable返回的是包装的数据
  • immer是原生数据

# 结合redux

immutable-redux

# 36.mobx和redux区别(都是react状态管理工具)?

  • mobx可以直接修改数据,不用返回一个新数据
  • mobx没有限制只能一个store,可以有多个
  • redux默认以对象形式存储数据,mobx可以使用可观察对象

# 核心

  • mobx通过observable可以精准更新
  • redux通过dispatch进行广播,然后provide和connect来对比前后差异控制更新

# 使用

let a = observable({name: 111}) => a.name = '222' 可以直接修改 =》通过autorun(()=>{})可以监听到改变 =》runInAction(()=>{})

# mobx-react

在render的时候provide(),具体组件@inject("store"),@observer=>会在this.props获取到store

# redux有哪些异步中间件

redux-sage 借助generator来处理异步,避免回调

# 37.了解过style-components吗?(将html标签包装成组件)

  • 声明const stFooter(标签名) = styled.footerbackground: yellow
  • 使用123
  • 高阶组件,通过props把参数传给子组件,子组件className={props.className}接收

# 38.createPortal作用?

是用来在父组件外创建dom节点的方法=》ReactDom.createPortal(child,container)

# 39.react的lazy和suspense作用?(路由懒加载)

懒加载(按需加载)=》webpack解析时会自动进行代码切割(code splitting),使用的时候才会被加载(异步)

const LazyLoad = (path) => {
    const Comp = React.lazy(() => import('../views/${path}'))
    // 通过Suspense来包裹
    return (
        <React.Suspense fallback={<>加载中...</>}>
            <Comp/>
        </React.Suspense>
    )
}
<Route path="/films" element={LazyLoad("film")}>
1
2
3
4
5
6
7
8
9
10

# 40.forwardRef用过吗?

用来向子组件传递ref

# 41.dva

dva = React-Router + Redux + Redux-saga

# 42.umi(乌米)

在umirc.ts中可以配置路由是否hash通过history,可以配置代理proxy

# react-router@6更新

  • 用element代替component
  • Routes代替Switch
  • useNavigate代替useHistory
  • 通过navigate或redirect组件替代路由重定向

# 自定义redirect组件进行重定向=》官方推荐

<Route path="*" element={<Redirect to="/file"/>}/>
function Redirect({to}) {
    const navigate = useNavigate()
    useEffect(() => {
        navigate(to,{replace: true})
    })
    return null
}
1
2
3
4
5
6
7
8

# api

  • useNavigate()
  • useParams()
  • useLocation()
  • useSearchParams()
  • useRoutes()

# this.props的三个参数

  • push=>useNavigate()
  • match=>useParams()
  • location=>useLocation()

# 自己封装withRouter

export default function withRouter(Component) {
    return function(props) {
        const push = useNavigate()
        const match = useParams()
        const location = useLocation()
        return <Component {...props} history={{push,match,location}}
    }
}
1
2
3
4
5
6
7
8

# 43.hooks实现一个计数器组件?

function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, []);

  return <h1>{count}</h1>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 44.createElement和cloneElement区别?

  • cloneElement根据element生成新的element=》element是一个react元素=》是不可变对象,只能访问不能修改(可以先拷贝再修改)
  • createElement根据type生成新的element=》type即html标签或react组件

# 45.useState和setState区别?

  • useState只能要在函数组件,setState只能用在类组件
  • useState修改state时,同一个useState声明的值会覆盖,而setState修改state,多次setState的对象会被合并处理=》多个useState声明的值会触发多次渲染 (useState修改state时,设置相同值不会重新渲染,而类组件即使setState相同的值也会重新渲染)

# 直接修改state会有什么影响?

状态发生了变化,但不会重新渲染,必须通过setState去更改才会重新渲染

# 46.使用hooks有什么常见的问题?

useState的时候,不能使用类似push,pop等来直接修改,否则数组无法获取到新值,要通过析构的方式

# 47.HOC(Higher Order Component)

  • 是一种基于react组合特性的设计模式
  • 用来实现逻辑复用(也可以用来把类组件转成函数组件),增删改props,渲染劫持(控制是否渲染,可以判断当数据加载完成后再去渲染内容->条件渲染-懒加载,权限控制,节流渲染-useMemo来减少渲染次数)
  • 高阶组件是参数为组件,返回值为新组件的函数=》核心思想要是纯函数,没有副作用
  • HOC不会修改传入的组件,是通过将组件包装在容器组件中来组成新的组件

# 应用

  • 利用条件渲染来做权限控制
  • 组件复用

# 优缺点

优点不会影响组件内部状态,缺点是大量使用HOC会产生很多嵌套,不好维护

# 组合 通过connect

connect是一个返回高阶组件的高阶函数=》输入类型和输出类型相同的函数很容易组合在一起

// 不推荐如下写法...
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))

// ... 建议编写组合工具函数
// compose(f, g, h) 等同于 (...args) => f(g(h(...args)))
const enhance = compose(
  // 这些都是单参数的 HOC
  withRouter,
  connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)
1
2
3
4
5
6
7
8
9
10
11

# 缺点

无法直接通过this.refs.component获取被HOC封装的组件=》可以通过父组件传递一个方法来获取

class ParentCompoent extends React.Component {
  getInstance = (ref)=>{
    this.wrappedInstance = ref;
  }

  render(){
    return <MyComponent getInstance={this.getInstance} />
  }
}
function HOCFactory(wrappedComponent) {
  return class HOC extends React.Component {
    render(){
      let props = {
        ...this.props
      };

      if(typeof this.props.getInstance === "function") {
        props.ref = this.props.getInstance;
      }

      return <wrappedComponent {...props} />
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 48.jsx和Fiber有什么关系?

  • mount阶段通过jsx对象(调用createElement的结果)调用createFiberFromElement生成Fiber
  • update时通过reconcileChildFibers或reconcileChildrenArray对比新jsx和老的Fiber(current Fiber)生成新的wip Fiber树

# 49.react17之前jsx文件为什么要声明import React from 'react',之后为什么不需要了?

jsx经过编译之后编程React.createElement,不引入React就会报错,react17改变了编译方式,变成了jsx.createElement

# 50.react中怎么使用async/await?

  • 在官方脚手架搭建的项目可以直接使用
  • 自己的项目需要引入babel,在babel中进行配置=>利用babel的transform-async-to-module-method来转成浏览器支持的语法

# 51.为什么hooks不能写在条件判断中?

hook会按顺序存储在链表中,如果写在条件判断中,就没法保持链表的顺序

# 52.render作用?

必须返回值,无论是否为空

# 53.react怎么优化?

  • import
  • React.lazy

# 54.请说一下react的渲染过程?

  • 性能瓶颈:cpu io fiber时间片 concurrent mode
  • 渲染过程:scheduler render commit Fiber架构

# 55.react有哪些优化手段?

shouldComponentUpdate、不可变数据结构、列表key、pureComponent、react.memo、useEffect依赖项、useCallback、useMemo

# 56.我们写的事件是绑定在dom上么,如果不是绑定在哪里?

v16绑定在document上,v17绑定在container上

# 57.为什么我们的事件手动绑定this?

合成事件监听函数在执行的时候会丢失上下文,拿到的this就是undefined,需要手动绑定this或者使用箭头函数

# 箭头函数作用

用来绑定组件的上下文

# 58.为什么不能用 return false 来阻止事件的默认行为?

合成事件和原生事件触发时机不一样

# 59.react怎么通过dom元素,找到与之对应的 fiber对象的?

通过internalInstanceKey对应

# 60.react Hooks比较比的是值还是内存地址?

比的是内存地址

# 61.手写防抖节流hook=》不使用useCallback,因为会把处理函数一起缓存起来,这样比如搜索用防抖节流,搜素值变化由于函数被缓存,就无法实时搜索

  • 直接使用防抖函数,这样搜索的时候,就可以每隔几秒才进行搜索
  • 如果是固定的值可以使用useCallback缓存
function useDebounce(fn, wait) { // 自研最优防抖
    let timer = useRef()
    return () => {
        if (timer.current) {
            clearTimeout(timer.current)
        }
        timer.current = setTimeout(() => {
            fn.apply(this, arguments)
        }, wait)
    }
}
1
2
3
4
5
6
7
8
9
10
11
function useDebounce(fn, wait) { // 自研最优节流
    let timer = useRef();
    return () =>{
        if (!timer.current) {
            timer.current = setTimeout(() => {
                timer.current = null
                fn.apply(this, arguments)
            }, wait)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 62.如何理解不可变值|性|数据(immutable)?

不可变数据是函数式编程的一个特性,意思是一旦创建就不能被修改的数据 =》对immutable对象任意修改操作都会返回一个新的immutable对象

# 使用immutable

通过immutable.fromJS把js对象转成immutable对象 setIn赋值,getIn取值

# 应用

setState和redux

# 好处

  • 使得复杂的特性更容易实现
  • 比较容易跟踪到数据改变
  • 确定react何时渲染=>pureComponent

# 63.如何在react中创建组件?

函数式组件

function Greeting({ message }) {
  return <h1>{`Hello, ${message}`}</h1>
}
1
2
3

类组件=>在函数内部对累组件处理需要通过new来实例化

class Greeting extends React.Component {
  render() {
    return <h1>{`Hello, ${this.props.message}`}</h1>
  }
}
1
2
3
4
5

# 64.状态提升?

多组件共享数据,要将数据提升到共同的祖先组件上,而不是在两个子组件中各自维护局部的状态

# 65.react中为什么使用className而不是class?

class是js的关键字,而jsx是基于js的

# 66.react的super和super(props)区别?

在构造函数内直接使用super拿不到props,必须使用super(props)将props传递给super方法 =》在构造函数之外,通过this.props都可以拿到

# 67.如何编写mapDispatchToProps()?

const mapDispatchToProps = (dispatch) => ({
 action: () => dispatch(action())
})
// 简写
const mapDispatchToProps = { action }
1
2
3
4
5

# 68.react事件绑定原理?

  • 事件绑定不是绑定在该div真实dom上,而是在document处监听所有的事件
  • 当事件发生并冒泡到document,会将事件内容交由真正的处理函数去执行

# react16和17事件区别

16 dom绑定在document上 17 dom绑定到container上

# 好处

减少内存损耗,在组件挂载和销毁时能统一绑定和移除事件

# 取消事件冒泡

不能通过event.stopPropagation,要调用event.preventDefault

# 69.react18有哪些更新

17 只有react事件批处理, 18 所有事件都自动批处理=》多次调用setState合并成一次 引入useInsertionEffect 在dom生成之后 返回值更新=》17 return null undefined报错=》18支持

# react源码划分

分成core,renderer,reconciler(包括了组件的挂载,卸载,更新等过程)