*受控组件 V.S 非受控组件
<FInput value={x} onChange={fn}/> 受控组件
<FInput defaultValue={x} ref={input}/> 非受控组件
*React 有哪些生命周期函数?分别有什么用?(Ajax 请求放在哪个阶段?)
初始化
constructor
挂载
* 2.render
* 3.componentDidMount
发生在 render 函数之后,已经挂载 Dom
更新
* props
* 2. shouldComponentUpdate(nextProps,nextState)
这个生命周期需要返回一个 Boolean 类型的值,判断是否需要更新渲染组件,优化 react 应用的主要手段之一,当返回 false 就不会再向下执行生命周期了,在这个阶段不可以 setState(),会导致循环调用。
* 4. render
执行 render 函数。
* 5. componentDidUpdate(prevProps, prevState)
在此时已经完成渲染,Dom 已经发生变化,State 已经发生更新,prevProps、prevState 均为上一个状态的值。
* state(具体同上)
* 1. shouldComponentUpdate
* 3. render
* 4. componentDidUpdate
卸载阶段
* componentWillUnmount
componentWillUnmount 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount 中创建的订阅等。componentWillUnmount 中不应调用 setState,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
在componentDidMount这个里面发送ajax请求
*React 如何实现组件间通信?
父子组件通信
父组件向子组件通信,可以通过 props 方式传递数据;也可以通过 ref 方式传递数据;
子组件向父组件通信,通过回调函数方式传递数据;
// 父组件
import React, { useState } from "react";
import Child from "./Child";
function Father() {
const [state, setState] = useState("state");
const changeName = () => {
setState("ade");
};
return (
<div>
<Child state={state} changeName={changeName} />
</div>
);
}
export default Father;
// 子组件
import React, { useState } from "react";
function Child(props) {
const { state } = props;
const onButtonClick = () => {
props.changeName();
};
return (
<>
<div>{state}</div>
<button onClick={onButtonClick}>click</button>
</>
);
}
export default Child;
非父子组件通信
父组件向后代所有组件传递数据,如果组件层级过多,通过 props 的方式传递数据很繁琐,可以通过 Context.Provider 的方式;
// 爷组件
import React, { useState, createContext } from "react";
import Child from "./Child";
export const C = createContext(null);
function Father() {
const [theme, setTheme] = useState("blue");
return (
<div>
<C.Provider value={{ theme, setTheme }}>
<p>{theme}</p>
<Child setTheme={setTheme}></Child>
</C.Provider>
</div>
);
}
export default Father;
// 孙组件import React, { useContext } from "react";import { C } from "./Father";function GrandSon() { const { theme, setTheme } = useContext(C); return ( <div> <h1>孙子组件</h1> <p>{theme}</p> <button onClick={() => setTheme("blue")}>蓝色</button> </div> );}export default GrandSon;
*shouldComponentUpdate 有什么用?
用于拦截组件渲染。返回true允许更新,false不更新
setState到底是异步还是同步?
setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。
setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
*虚拟 DOM 是什么?React diff 的原理是什么?
在 React 中,render 执行的结果得到的并不是真正的 DOM 节点,结果仅仅是轻量级的 JavaScript 对象,我们称之为 virtual DOM。
如果没有 Virtual DOM,简单来说就是直接重置 innerHTML。
比较 innerHTML 和 Virtual DOM 的重绘过程如下:
innerHTML: render html string + 重新创建所有 DOM 元素
Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新
虚拟DOM的原理
- 虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象
- 状态变更时,记录新树和旧树的差异
- 最后把差异更新到真正的dom中
React diff与传统diff区别
传统diff通过循环递归对节点进行依次对比,效率低下,算法复杂度达到 O(n^3),其中 n 是树中节点的总数。O(n^3) 到底有多可怕,这意味着如果要展示1000个节点,就要依次执行上十亿次的比较。
React 分别对 tree diff、component diff 以及 element diff 进行算法优化。
tree diff
利用Virtual DOM树进行 分层比较、层级控制。如果出现跨层次的移动操作,会直接删除之前的,在需要的地方重新创建。
component diff
- 对于同一类组件,一方面:按照原策略继续比较 virtual DOM tree。另一方面:可以人为的用shouldComponentUpdate()判断Virtual DOM是否发生了变化,若没有变化就不需要在进行diff,这样可以节省大量时间,若变化了,就对相关节点进行update。
- 对于非同一类的组件,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。(注意是直接删除,然后创建)
element diff
React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。没有key 的直接使用diff差异化对比,删除后创建新的。
React 通过设置唯一 key的策略,对 element diff 进行算法优化,如果有key,节点位置不同,就进行位置移动。
*什么是高阶组件?
高阶组件本身不是一个组件, 而是一个函数 ,这个函数的参数是一个组件, 返回值是一个组件
ref的转发(获取函数式组件DOM)
// 父组件import React, { useRef, forwardRef, useEffect } from "react";import GrandSon from "./GrandSon";const GrandSon2 = forwardRef(GrandSon);function Child(props) { const { setTheme } = props; const sonRef = useRef(null); useEffect(() => { console.log(sonRef.current); }, []); return ( <> <h1>儿子组件</h1> <button onClick={() => setTheme("red")}>红色</button> <GrandSon2 ref={sonRef} /> </> );}export default Child;
// 子组件import React, { useContext } from "react";import { C } from "./Father";function GrandSon(props, ref) { const { theme, setTheme } = useContext(C); return ( <div> <h1>孙子组件</h1> <p>{theme}</p> <button ref={ref} onClick={() => setTheme("blue")}> 蓝色 </button> </div> );}export default GrandSon;
常用的高阶组件有属性代理。
function HOC(WrapComponent){ return class MyName extends React.Component{ state={ name:'ade' } render(){ return <WrapComponent { ...this.props } { ...this.state } /> } }}
HOC的作用
props的增强 :不修改原有代码的情况下,添加新的props
渲染劫持:在开发中,我们可能遇到这样的场景:某些页面是必须用户登录成功才能进行进入;如果用户没有登录成功,那么直接跳转到登录页面。
修改渲染树
* Redux 是什么?
Redux 是一个应用状态管理js库
React-Redux 是连接 React 应用和 Redux 状态管理的桥梁。React-redux 主要专注两件事,一是如何向 React 应用中注入 redux 中的 Store ,二是如何根据 Store 的改变,把消息派发给应用中需要状态的每一个组件。
核心概念
Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store。
State:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种时点的数据集合,就叫做State。
Action:State的变化,会导致View的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。
Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator。
Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数,它接受Action和当前State作为参数,返回一个新的State。
dispatch:是View发出Action的唯一方法。
核心api
createStore
createStore
redux中通过 createStore 可以创建一个 Store ,使用者可以将这个 Store 保存传递给 React 应用,具体怎么传递那就是 React-Redux 做的事了。首先看一下 createStore 的使用:
const Store = createStore(rootReducer,initialState,middleware)
-
参数一 reducers : redux 的 reducer ,如果有多个那么可以调用 combineReducers 合并。
-
参数二 initialState :初始化的 state 。
-
参数三 middleware :如果有中间件,那么存放 redux 中间件。
combineReducers
/* 将 number 和 PersonalInfo 两个reducer合并 */const rootReducer = combineReducers({ number:numberReducer,info:InfoReducer })
- 正常状态可以会有多个 reducer ,combineReducers 可以合并多个reducer。
applyMiddleware
const middleware = applyMiddleware(logMiddleware)
- applyMiddleware 用于注册中间价,支持多个参数,每一个参数都是一个中间件。每次触发 action ,中间件依次执行。
connect 的原理是什么?
connect 提供的功能,做数据获取,数据通信,状态派发等操作。
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
mapStateToProps:组件依赖 redux 的 state,映射到业务组件的 props 中,state 改变触发,业务组件 props 改变,触发业务组件更新视图。
mapDispatchToProps:将 redux 中的 dispatch 方法,映射到业务组件的 props 中。
mergeProps:stateProps , state 映射到 props 中的内容 ; dispatchProps, dispatch 映射到 props 中的内容; ownProps 组件本身的 props。
// ComponentAimport React, {useState} from 'react'import {connect} from 'react-redux'function ComponentA({toCompB, compBsay}) { /* 组件A */ const [CompAsay, setCompAsay] = useState('') return ( <div className="box"> <p>我是组件A</p> <div> B组件说:{compBsay} </div> 对B组件说:<input placeholder=" 对B组件说" onChange={(e) => setCompAsay(e.target.value)}/> <button onClick={() => toCompB(CompAsay)}>确定</button> </div> )}/* 映射state中CompBsay */const CompAMapStateToProps = state => ({compBsay: state.info.compBsay})/* 映射toCompB方法到props中 */const CompAmapDispatchToProps = dispatch => ({toCompB: (mes) => dispatch({type: 'SET', payload: {compAsay: mes}})})/* connect包装组件A */const ConnFather = connect(CompAMapStateToProps, CompAmapDispatchToProps)(ComponentA)export default ConnFather
// ComponentBimport React, {useState} from 'react'import {connect} from 'react-redux'function ComponentB(props) { /* B组件 */ const [compBsay, setCompBsay] = useState('') const handleToA = () => { props.dispatch({type: 'SET', payload: {compBsay: compBsay}}) } return ( <div className="box"> <p>组件B</p> <div> A组件对说:{props.compAsay} </div> 对A组件说:<input placeholder="对A组件说" onChange={(e) => setCompBsay(e.target.value)}/> <button onClick={handleToA}>确定</button> </div> )}/* 映射state中 CompAsay */const CompBMapStateToProps = state => ({compAsay: state.info.compAsay})const ConnSon = connect(CompBMapStateToProps)(ComponentB)export default ConnSon
你是如何理解fiber的?
Fiber是对React核心算法的重构
Fiber 把一个渲染任务分解为多个渲染任务,而不是一次性完成,把每一个分割得很细的任务视作一个"执行单元",React 就会检查现在还剩多少时间,如果没有时间就将控制权让出去,故任务会被分散到多个帧里面,中间可以返回至主进程控制执行其他任务,最终实现更流畅的用户体验。
推荐阅读:完全理解React Fiber | 黯羽轻扬 (ayqy.net)
参考文章
React 源码剖析系列 - 不可思议的 react diff