«
React面试

时间:2022-6   


概述

Hello, React :-)

React 是用于构建用户界面的 JavaScript 库,React 核心只关注视图,不断优化算法,改进性能,提高开发和交互体验。

React 迭代稳定,重视兼容和过渡,在国内外,尤其是南方,都有相当多的公司在使用 React。

渐进式的思想同样表现在 React 的学习曲线上,能够与传统的 Web 技术共存,灵活的 JSX 语法等都会让 React 上手很快, 而庞大生态赋予了 React 更强能力的同时,也让开发者感叹花费了更多时间在社区里遨游。

React 应用

React 在前端开发领域应用广泛,使用 React 可以构建 Web,插件,单页应用,App,小程序,桌面端,服务端等,微服务,Serverless,低代码,虚拟现实等都有 React 的用武之地。

React 面试注意事项

React 面试题可以分为以下 4 个方面

新手最好先熟练 ES5+ 的使用,再上手 React 时,可以边阅读边写代码,适当练习若干项目,再看原来生涩的表述,会有亲切的画面感。带着项目去看面试题,联想练习或工作中遇到的实际问题,加上自己的理解,你的回答一定可以比手册总结得更自然,更能得到面试官的肯定。

认识

对比 React 和 Vue

相同点

不同点

什么是 React

React 是用于构建用户界面的 JavaScript 库

对比 React 和 Angular

你认为 React 的缺点是什么

什么是声明式编程

什么是函数式编程

MVC 和 MVVM 的区别是

相同点

如何组织 React 项目文件结构?

React 建议

React 18 都有哪些新特性?

JSX

什么是 JSX ?

为什么推荐在 React 中使用 JSX ?

为什么 JSX 可以有效降低 XSS 风险?

如何在 JSX 中条件渲染?

render() {
    if (condition) return <div />
    else return null
}
render() {
    return condition ? <div /> : null
}
render() {
    return condition && <div />
}
render () {
    const Condition = props => {
        const { If, children } = props
        return If && children
    }
    return <Condition If={true}><div /></Condition>
}

如何在 JSX 中循环控制?

render() {
    const { data } = props
    return data.map(item => <b id={item.id}>{item.text}</b>)
}
render() {
    const { data } = props
    const datas = Array(data.length)
    for (let i = 0; i < data.length; i++) {
        const item = data[i]
        datas[i] = <b id={item.id}>{item.text}</b>
    }
    return datas
}
render() {
    const { data } = props
    return _.times(data.length, i => <b id={data[i].id}>{data[i].text}</b>)
}

为什么 JSX 中 class 变成了 className ?

组件

什么是 React 组件?

React 组件分成哪几类?

类组件和函数组件的区别是?

说明 类组件 函数组件
回调钩子 生命周期 useEffect / useLayoutEffect
this 有,事件处理函数需绑定 this
state 有,this.setState 更新 无,useState / useReducer 引入
实例化
性能 现代浏览器中,闭包和类的原始性能只有在极端场景才会有明显差别 使用 Hooks 某些情况更加高效,避免了 class 需要的额外成本,如创建类实例和在构造函数绑定事件处理器的成本,符合语言习惯的代码不需要很深的组件库嵌套

受控组件和非受控组件的区别是?

什么是高阶组件?

什么是 Pure Compoents ?

React.PureComponent 与 React.Component 相似,区别是

展示组件和容器组件的区别是?

React 组件按照用途可以分为展示组件和容器组件

React 推荐所有新组件,无论是展示组件,还是容器组件,都采用函数组件 + Hook 方式编写

React 分离展示组件和容器组件的优势

如何劫持 React 组件提高组件复用度?

劫持 React 组件又被称为渲染劫持

将已有组件包装,注入新属性和功能,输出高阶组件,来实现组件复用

劫持需要遵守高阶组件的约定

如何设计一个 React 组件?

React 组件与 Web Components 共存的最佳实践是?

状态

什么是 React 的状态?

什么是 React 的状态提升?

状态和属性的区别是什么?

相同点

不同点

如何创建动态的状态名称?

const h = {}
let i = 0
h[i + 1] = i // h = {'1': 0}
const { i } = props // props = { i : 0 }
this.state = {
    [i + 1] : i
} // this.state = { '1' : 0 }

setState 支持哪些用法?

setState 和 replaceState 的区别是?

如何优化 setState,减少不必要更新?

示例代码

getData = data => {
    const { time } = data
    this.setState(state => {
        return state.time === time ?  null : { time }
    })
}

当 State 值为 Object 时,如何优化?

属性

什么是 React 的属性?

为什么不能直接修改属性?

通过属性传递组件本身的方法有哪些?

使用 key 属性有哪些注意事项?

如何在 React 中进行静态类型检查?

如何限制某个属性是必须的?

React 中,可以在任何 PropTypes 属性后加上 isRequired,声明该属性必须

如何设置属性的默认值?

React 中,通过特定 defaultProps 属性来定义 props 默认值

React 是否支持 HTML 属性?

React 是否支持自定义属性?

通信

React 父子组件通信有哪些方法?

export default class Eventbus {
    constructor () {
        this.fns = []
    }
    emit (type, ...args) {
        const fns = this.fns[type]
        if (fns) fns.forEach(fn => fn.apply(this, args))
    }
    addListener (type, fn) {
        const fns = this.fns[type]
        fns ? fns.push(fn) : (this.fns[type] = [fn])
    }
}

使用 Node.js 的 events

为什么 React 是单向数据流?

什么是 Context ?

什么是 ContextType ?

ContextType 用于订阅单一的 context

const MyContext = React.createContext(defaultValue)
class NewClass extend React.Component {
    render() {
        const value = this.context
    }
}
NewClass.contextType = MyContext
const MyContext = React.createContext(defaultValue)
class NewClass extend React.Component {
    static contextType = MyContext
    render() {
        const value = this.context
    }
}

在 React 组件中,使用 this.context 访问通过 contextType 指定的 Context

如何优化 Context ?

Context 的 value 更新,它内部所有消费组件都会重新渲染,并且不受制于 shouldComponentUpdate 函数

context 使用参考标识(reference identity) 来决定渲染时机,当 Provider 接收 value 为对象字面量时,每次 value 都会被赋值新对象,建议将 value 状态提升到父节点的 state 里

拆分 Context

将原来同一个 Context 频繁变化的值拆分出来,分别放入不同的 Context

记忆化

使用 React.memo 或 useMemo 包裹组件,指定需要比较的值,只有值变化时重新渲染

使用 createContext 的第二参数,不稳定,不推荐

createContext 为函数,两个形参分别为更新前后值,接收 Number 类型作为返回值

在订阅 context 变化的组件上,使用 unstable_observedBits 设置重新渲染组件的条件

const { Consumer, Provider } = React.createContext(null, (prev, next) => {
    if (prev.value1 !== next.value1) return 1
    if (prev.value2 !== next.value2) return 10
})
const Consumer1 => { // 只有当 value1 变化重新渲染
    return <Consumer unstable_observedBits={1} children={value => (
<>{value}</>
)}
/>
}
const Consumer2 => {// 只有当 value2 变化重新渲染
return <Consumer unstable_oberverdBits={10} children={value => (
<>{value}</>
)}
/>
}

使用第三方库
use-context-selector
使用 createContext 创建支持 useContextSelector 的特殊 context
使用 useContextSelector 选择 context 某个值,当且仅当该值变化时,重新渲染
React Tracke

什么是 Ref 转发?

const newComponent = React.forwardRef(function myName(props, ref) {
    return <innerComponent {...props} forwardedRef={ref} />
})
// DevTools DisplayName: ForwardRef(myName)

设置函数的 displayName 来包含被包裹组件的名称

function myName(props, ref) {
    return <innerComponent {...props} forwardedRef={ref}
}
myName.displayName = 'myDisplayName'
const newComponent = React.forwardRef(myName)
// DevTools DisplayName: ForwardRef(myDisplayName)

渲染

React 返回空对象有哪些方法?

render() {
    return false / true / null /undefined
}
render() {
    return <React.Fragment /> / </>
}

如何优化不必要的渲染?

React 如何渲染 HTML ,有什么风险?

function newComponent() {
    return <div dangerouslySetInnerHTML={{__html: '<b>1</b>'}} />
}

React 为什么要引入基于 Fiber 协调器的异步渲染?

什么是 React Fiber ?

React Fiber 异步渲染分为哪几个阶段,对应生命周期是什么?

不同于 Stack renconciler,Fiber reconciler 过程分为两个阶段:

对应生命周期

生命周期

React 组件有哪些生命周期方法?

React 每个组件都包含“生命周期”方法

开发者可以重写这些方法,便于在运行过程中特定阶段执行这些方法

React 组件的生命周期可分为哪些阶段?

React 组件的生命周期可以分为三个阶段

异步数据请求应在哪些生命周期里调用?

useEffect useLayoutEffect 与生命周期的对应关系是?

useLayoutEffect

useEffect

两者都适用于在函数组件主体内,即 React 渲染阶段改变 DOM,添加订阅,设置定时器,记录日志以及执行其他包含副作用的操作

优先使用 useEffect 避免阻塞视觉更新,只在需要读取 DOM 布局,在浏览器绘制前,同步触发重渲染的场景使用 useLayoutEffect

在 constructor 中使用 super 的意义是?

class MyComponent extends React.Component {
    state = { time : 0 }
}

对比 React Hook 与生命周期

事件处理

React 和 DOM 事件处理的区别是?

React 元素的事件处理和 DOM 元素的不同点

什么是 React 合成事件?

如何在 React 事件处理阻止默认行为?

调用e.preventDefault阻止默认行为

如何解决 类组件 中,事件处理的 this 为 undefined 的问题?

如何传参给事件处理函数?

两种方式

如何阻止事件处理函数被频繁调用?

const throttle = (fn, delay) => {
    let timer = null
    return function (...args) {
        if (timer) return
        timer = setTimeout(() => {
            timer = null
            fn.apply(this, args)
        }, (delay + '') | 0 || 1000 / 60)
    }
}

原生 + React Hook

const useThrottle (fn, delay) => {
    const ref = useRef(fn)
    useEffect(() => {
        ref.current = fn
    })
return useCallback(
throttle((...args) => ref.current(...args), delay),
[delay]
)
}
const debounce = (fn, delay) => {
    let timer = null
    return function (...args) {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            timer = null
            fn.apply(this, args)
        }, (delay + '') | 0 || 1000 / 60)
    }
}

原生 + React Hook

const useDebounce (fn, delay) => {
    const ref = useRef(fn)
    useEffect(() => {
        ref.current = fn
    })
    return useCallback(
    debounce((...args) => ref.current(...args), delay),
    [delay]
    )
}

React 17 对事件处理做了哪些改进?

样式管理

如何在 React 中使用样式?

如何按条件加载样式?

style 采用小驼峰命名属性的 JavaScript 对象,可以按照 JavaScript 对象的方式,有条件地改变属性名和属性值

const style = {
    [2 > 1 ? 'fontSize' : 'lineHeight'] : 2 > 1 ? 12 : 1
} // { fontSize: 12 }
function Component () {
    return <div style={style}>Hello World!</div> // style="font-size: 12px"
}

className 属性,可以传入 JavaScript 表达式,有条件改变的类名

const className = 2 > 1 ? 'menu-active' : 'menu'
function Component () {
    return <div className={className}>Hello World!</div> // class="menu-active"
}

以styled-components 为例

模板字符串

const Button = styled.button`
background: ${props => props.primary ? 'palevioletred' : 'white'},
` // background: palevioletred
function Component () {
    return <Button>Hello World!</Button>
}

JavaScript 对象

const Button = styled.div(props => ({
    background: props.primary ? 'palevioletred' : 'white'
})); // background: palevioletred
function Component () {
    return <Button>Hello World!</Button>
}

如何合并多个内联样式?

style采用小驼峰命名属性的 JavaScript 对象

任何合并 JavaScript 对象的方法都可以用于合并内联样式

const backgroundStyle = { background: 'red' }
const colorStyle = { color: 'red' }
function Component () {
    return <div style={{ ...backgroundStyle, ...colorStyle }}></div>
}
const backgroundStyle = { background: 'red' }
const colorStyle = { color: 'red' }
function Component () {
    return <div style={Object.assign(backgroundStyle, colorStyle)}></div>
}
const backgroundStyle = { background: 'red' }
const colorStyle = { color: 'red' }
Object.keys(colorStyle).forEach(key => backgroundSyle[key] = colorStyle[key])
function Component () {
    return <div style={backgroundStyle}></div>
}

如何模块化样式,如何避免样式名冲突 ?

(1)什么是 CSS 模块化?

CSS 模块化是将 CSS 规则 拆分成相对独立的模块,便于开发者在项目中更有效率地组织 CSS

CSS 模块化的方式

无论哪种方式,核心都是通过 保证CSS类命名唯一,或者 避免命名使用内联样式,来模拟出CSS模块作用域的效果

(2)基于文件的 CSS 模块的加载

(3)CSS 模块化的实现方式

(4)React 中的样式模块化

React 对样式如何定义,没有明确的态度,如果存在疑虑,比较好的方式是和平常一样,在一个单独的 *.css 定义你的样式,并通过 className 指定它们。此时,你可以参考 CSS 模块化的方案,通过文件或样式命名规则,来实现样式模块化

行内样式 style 是避免样式命名冲突的经典策略,但由于性能比 className 低,React 通常不推荐将 style 属性作为设置元素样式的主要方式。在多数情况下,应使用 className 属性来引用外部 CSS 样式表重定义的 class。style 在 React 应用中多用于在渲染过程中添加动态计算的样式

同样地,在 CSS-in-JS 中,早期有基于 style 实现的 Radium 库,现在比较流行的是基于随机 className 的 style-components 库,与之类似的有 Emotion 和 Glamor,还有面向愿意将 CSS 和 JavaScript 分开存放开发者的 JSS 等

错误边界

什么是 React 错误边界?

(1)为什么要有错误边界?

部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决整个问题,React 16 引入了错误边界的概念

(2)什么是错误边界?

错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。

错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

(3)如何构建一个错误边界组件?

如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界,其中

使用只需用错误边界组件包裹 React 组件

错误边界可以捕获什么错误?

错误边界可以捕获子组件的错误,包括子组件

以上其中的错误

错误边界不能捕获什么错误?

错误边界不能捕获以下场景产生的错误:

错误边界应该放置在什么位置?

错误边界的位置通常由开发者决定,可以用错误边界组件包裹

组件的哪些生命周期可以用于错误捕获?

组件有两个声明周期方法可以用于错误捕获

componentDidCatch(error, info)

此生命周期在后代组件抛出错误后被调用

它将抛出的错误作为第一参数 error

它将带有 componentStackkey 对象,即包含有关组件引发错误的栈信息作为第二参数 info

此生命周期会在提交阶段调用,允许执行副作用,如打印、记录或上报错误日志等

React 如何处理未捕获错误,为什么这样处理?

(1)React 如何处理未捕获错误

自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。在浏览器环境使用 ReactDom 渲染的页面将展示为白屏,并在控制台输出报错信息

(2)为什么这样处理?

React 认为把一个错误的 UI 留在那比完全移除它更糟糕。在即时通信应用中展示错误的消息,在支付类应用中展示错误的金额,都比不呈现任何内容更糟糕

白屏有助于开发者发现已存在但未曾注意到的崩溃,手动增加错误边界,让开发者应用发生异常时提供更好的用户体验

React 推荐使用 JS 错误报告服务,了解在生产环境中出现的未捕获异常,并将其修复

如何处理事件处理函数内部错误?

错误边界无法捕获事件处理函数内部的错误,因为事件处理不会在渲染期间触发,即使异常,React 仍然能够知道需要在屏幕上显示什么

捕获事件处理函数内部的错误,使用 JavaScript 的 try / catch 语句即可

事实上,我们可以用 window.addEventListener('error', () => {})捕获大部分同步、定时器、Generator 异步错误,用 window.addEventListener('unhandledrejection')捕获 Promise 及其语法糖 async / await 错误

Hook

什么是 React Hook?

Hook 是 React 16.8 的新增特性

什么是 State Hook?

什么是 Effect Hook?

如何清除 Effect Hook 的副作用?

Hook 本质是 JavaScript 函数,使用 Hook 时需要遵循两条规则

如何校验 Hook 是否遵循规则?

React 提供了一个名为 eslint-plugin-react-hooks 的 ESLint 插件来校验 Hook 是否遵循规则

npm install eslint-plugin-react-hooks -D
{
    "plugin": [
    // ...
    "react-hooks"
    ],
    "rules": {
        // ...
        "react-hooks/rules-of-hooks": "error", // 检查 Hook 规则
        "react-hooks/exhaustive-deps": "warn" // 检查 effect 依赖
    }
}

useMemo 和 useCallback 的区别是?

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

返回一个memoized值,把"创建"函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销计算。
useCallback

const memoizedCallback = useCallback(() => { doSomething(a, b) }, [a, b])

返回一个memoized回调函数,把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
useCallback 可看作函数组件的shouldCompoentUpdate,使用引用相等性避免非必要渲染

useCallback 返回的是函数,useMemo 返回的是值,也可以是函数

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

useReducer 和 useState 的区别是?

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init)

useReducer 是 useState 的替代方案,它接收一个形如(state, action) => newState的 reducer,返回当前的 state 及其配套的dispatch方法
useReducer 比 useState 更适用于某些场景

useReducer 第二个返回值是 dispatch 而不是回调函数

useLayoutEffect 和 useEffect 的区别是?

useLayoutEffect

useLayoutEffect(() => {
    // add effect
    return cleanup() {
        // clean effect
    }
})

相同

区别

useRef 和 Refs 的区别是?

useRef

const refContainer = useRef(initialValue)

useRef 返回一个可变的 ref 对象,其.current属性被初始化传入参数initialValue
返回的 ref 对象在组件的整个生命周期保持不变

Refs

class Component extends React.Component {
    constructor(props) {
        super(props)
        this.myRef = React.createRef()
    }
    render() {
        return <div ref={this.myRef} />
    }
}

Refs 使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素
当 ref 被传递给 render 中的元素时,对节点的引用可以在 ref 的 current 属性中被访问

区别

useRef

Refs

如何自定义 Hook?

(1)自定义 Hook 定义

自定义 Hook,是将组件逻辑提取到可重用的函数,它可以像 render props 和高阶组件来共享组件之间的状态逻辑,而不增加组件

(2)如何自定义 Hook

自定义 Hook 是一个函数,可以调用其他的 Hook

将两个函数组件的共同的状态逻辑提取到自定义 Hook 中,自定义 Hook 是一种自然遵循 Hook 设计的约定,而不是 React 的特性

(3)自定义 Hook 作用

自定义 Hook 解决了以前在 React 组件中无法灵活共享逻辑的问题

自定义 Hook 可以用于表单处理、动画、订阅声明、计时器等场景

尽量避免过早地增加抽象逻辑,当函数组件代码行数增多时,可以通过自定义 Hook 简化代码逻辑,解决组件杂乱无章

是否有必要使用 Hook API 重写所有类组件?

不,React 推荐称为编写 React 组件的主要方式,并且提供了自定义 Hook 等 class 组件无法实现的功能,更利于自动代码优化。但是

所以,React 鼓励写新组件的时候开始尝试 Hook,不推荐用 Hook 重写已有的 class,除非开发者本来就打算重写它们(例如:为了修复 bug)

useState 返回更新 state 的函数是同步,还是异步的?

useState 返回更新 state 函数式异步的,接收参数可以是新状态的值,也可以是回调函数

可以通过两种方式,获取上一个 state 的状态值

测试

什么是 Jest ?

Jest 是一款 JavaScript 测试运行器,零配置,支持快照,并行,迭代快,生态丰富

如何模拟数据获取?

模拟数据获取

import axios from 'axios'
jest.mock('axios')
test('Test axios', async () => {
    axios.get.mockResolvedValueOnce('data')
    const data = await axios.get('/api')
    excpet(data).toBe('data')
})
test('Test fetch', async () => {
    jest.spyOn(global, 'fetch').mockImplementation(() =>
    Promise.resolve({
        json: Promise.resolve('data')
    })
)
    const response = await fetch('/api')
    const data = await response.json()
    excepet(data).toBe('data')
})

如何模拟组件?

可以用 jest.mock 来模拟组件

jest.mock('./common/MyComponent', () => props => <>{props.text}</>)

如何模拟计时器?

如何模拟浅层渲染?

浅层渲染即只渲染父组件而不渲染所有子组件,可以只渲染组件的“第一层”

浅层渲染的实现

组件:MyCompoent.js

import React form 'react'
const MyComponent = () => <><div></div></>
import { shallow } from 'enzyme'
import MyComponent from './MyComponent'
describe('shadow render', () => {
    const myComponent = shallow(<MyComponent />)
    expect(myComponent.contains(<div></div>)).toEqual(true)
})
import ShallowRenderer from 'react-test-renderer/shallow'
const renderer = new ShallowRenderer()
renderer.render(<MyComponent />)
const result = renderer.getRenderOutput()
expect(result.props.children).toEqual([<div></div>])

如何将组件渲染成 JS 对象?

react-test-renderer 与 jest 的快照功能一起用于简化 React 组件测试

import renderer from 'react-test-renderer'
test('Link renders correctly', () => {
    const tree = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>
    ).toJSON()
    expect(tree).toMatchSnapshot()
})
exports[`Links renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function bound _onMouseEnter]}
onMouseLeave={[Function bound _onMouseLeave]}
Facebook
</a>

如何模拟 DOM 环境?

测试通常在无法访问真实渲染表面(如浏览器)的环境中运行

建议使用 jsdom 来模拟浏览器,这是一个在 Node.js 内运行的轻量级浏览器实现

综上,可以使用 Jest 作为测试运行器,渲染到 jsdom,使用 act() 辅助函数提供的能力通过一系列的浏览器事件来模拟用户交互行为,此外

如何获得测试代码覆盖率?

前端 javascript 测试代码覆盖率的常用工具为 istanbul

使用 jest 框架,可以传入参数 --coverage 获得测试代码覆盖率

如何测试 React Router?

测试 React Router,需要先使用 createMemoryHistory() 来创建导航历史,模拟点击导航按钮,等待页面 DOM 加载,判断应加载页面的独有元素是否被加载

以 @tesing-library/react 为例

App.jsx

import React from 'react'
import { Switch, Route, Link } from 'react-router-dom'
export App = () => {
    return (
    <>
    <nav>
    <Link data-testid="home-link" to="/">Home</Link>
    <Link data-testid="about-link" to="/about">About</Link>
    </nav>
    <Siwtch>
    <Route path='/' component={Home} /> // contains <img id="home" />
    <Route path='/about' component={About} /> // contains "About Company" text
    </Siwtch>
    </>
    )
}

App.test.js

import React from 'react'
import { Router } from 'react-router-dom'
import { render, fireEvent } from '@tesing-library/react'
import { createMemoryHistory } from 'history'
import App from './App'
import '@testing-library/jest-dom/extend-expect'
const renderRouter = Component => {
    const history = createMemoryHistory()
    return render(<Router history={history}>{Component}</Router>)
}
describe('App Test', () => {
    it('render home page', () => {
        const { getByTestId } = renderRouter(App)
        expect(getByTestId('home')).toBeInTheDocument()
    })
    it('render about page', () => {
        const { container, getByTestId } = renderRouter(App)
        fireEvent.click(getByTestid('about-link'))
        expect(container.innerText).toHaveTextContent('About Company')
    })
})

原理

什么是 Virtual DOM ?

Virtual DOM 是一编程概念

Virtual DOM 赋予 React 声明式的 API

Virtual DOM 是一种模式,在 React 中

什么是 React Diff,对比 Vue Diff ?

虚拟 DOM 的 Diff 算法

React 基于以下两个假设的基础之上提出 O(n) 的启发式算法

React Diffing 算法

Vue 2.x 优化 Diff 算法

Vue 3.x 优化 Diff 算法

什么是 React Concurrent 模式?

Concurrent 模式是一组 React 的新功能,可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整

Concurrent 模式的特点包括

Concurrent 模式的任务是帮助将人机交互研究的结果整合到真实的 UI 中

Concurrent 模式的开启

npm install react@experimental react-dom@experimental

Concurrent 模式常用 API

什么是 Suspense ?

React 如何定义任务的优先级?

任务优先级是产生更新对象之后,React去执行一个更新任务,这个任务的优先级

任务优先级被用来区分多个更新任务的紧急程度,对比前后两次更新的任务优先级

简而言之,任务优先级存在的意义

React定义的任务优先级分为三类

LanePriority 越大,优先级越高

export const SyncLanePriority: LanePriority = 17
export const SyncBatchedLanePriority: LanePriority = 16
const InputDiscreteHydrationLanePriority: LanePriority = 15
export const InputDiscreteLanePriority: LanePriority = 14
const InputContinuousHydrationLanePriority: LanePriority = 13
export const InputContinuousLanePriority: LanePriority = 12
const DefaultHydrationLanePriority: LanePriority = 11
export const DefaultLanePriority: LanePriority = 10
const TransitionShortHydrationLanePriority: LanePriority = 9
export const TransitionShortLanePriority: LanePriority = 8
const TransitionLongHydrationLanePriority: LanePriority = 7
export const TransitionLongLanePriority: LanePriority = 6
const RetryLanePriority: LanePriority = 5
const SelectiveHydrationLanePriority: LanePriority = 4
const IdleHydrationLanePriority: LanePriority = 3
const IdleLanePriority: LanePriority = 2
const OffscreenLanePriority: LanePriority = 1
export const NoLanePriority: LanePriority = 0

Redux

什么是 Redux

Redux 是 JavaScript 应用的可预测状态容器

Flux 和 Redux 的区别是?

Flux

Redux

综上,Redux 与 Flux 都基于单向数据流,架构相似,但 Redux 默认应用只有唯一 Store,精简掉 Dispatcher,引入 Reducer 纯函数,通过返回新对象,而不是更改对象,更新状态。

对比 Flux 的官方实现,Redux 的 API 更简洁,并且提供了如 combineReducers等工具函数及 React-Toolkit 工具集,以及对状态的撤销、重做和持久化等更复杂的功能。提供如 React-Redux 等简化 Redux 与其他第三方库的连接。

Facebook 官方推荐在生产环境中使用代替 Flux。

Redux 的核心原则是?

Redux 设计和使用遵循三个基本原则:

React Context 和 Redux 的区别是?

ReactContext

xxxxxxxxxx constMyContext= React.createContext(){ Provider, Consumer }= MyContext   

使用<Provider value={``v``a``lu``e}>组件去声明想要传递的数据源

消费数据

​ 高阶组件写法 <Consumer>{``v``aule=> ... }</Consumer>

​ Hook写法 const value = useContext(MyContext)

Redux

const reducer =(state, action) =>{
  switch(action.type){
    case 'INCREMENT':return state +1
    case 'DECREMENT': return state -1
    default: returnstate
  }
}

创建store 对象

import {createStore }from 'redux'
conststore= createStore(reducer)

使用 state 和 dispatch

const MyComponent = ({ value, onIncrement, onDecrement })=> (
 <>
   {value}
    <button onClick={onIncrement}>+</button>
    <button onClick={onDecrement}>-</button>
  </>
)
const App = ()=> (
 <MyComponent
 value={store.getState()}
     onIncrement={() => store.dispatch({type: 'INCREMENT'})}
     onDecrement={()=> store.dispatch({type: 'DECREMENT'})}
  />
)

区别

React 访问 ReduxStore 的方法有哪些?

import React from 'react'
import { connect }from 'react-redux'
const MyComponent = ({value}) => <>{value}</>
const mapStateToProps= ({value}) => ({value})
export connect(mapStateToProps)(MyComponent)
import { createStore }from 'redux'
import reducer from './reducer'
conststore =createStore(reducer)
export defaultstore

引入sotre 通过 getState获取状态

import store from'./store'
export function get(){
  const state = store.getState()
  const { value } = state
}
import { createStore, applyMiddleware }from 'redux'
import thunkfrom 'redux-thunk'
import reducer from'./reducer'
const store = createStore(reducer,applyMiddleware(thunk))
const get = () => (dispatch, getState) =>{
  dispatch({type: 'getStart'})
  const { token }= getState()
  fetch('/user/info',{
method: 'GET',
    header:{
      Authorization: `Bearer ${token}`
    }
  }).then(res=>res.json()).then(json =>{
    dispatch{type: 'getSuccess', payload: {user: json } }
  })
}
store.dispatch(get())
import { createStore, applyMiddleware } from 'redux'
import reducer from './reducer'
constpayload = null
const myMiddleware = store =>next =>action=>{
  if (typeof action === 'function') {
    return action(store)
  }else{
    payload= action.payload
  }

  return next(action)
}
const store = createStore(reducer, applyMiddleware(myMiddleware))
store.dispatch(store=> store.getState())// 直接调用 store的方法
store.dispatch({
  type: 'start',
payload : 'payload'
})
console.log(payload) // payload

Redux 中异步请求数据时发送多 Action 方法有哪些?

异步请求数据等异步操作通常要发出三种 Action

发送多 Action 方法

const mapDispatchToprops = dispatch=>{
  return {
    asyncAction (){
      dispatch({ type: 'start' })
      fetch('/api').then(res => res.json).then(data=> {
        dispatch({type: 'success',payload: { data } })
      }).catch(error=> {
        dispatch({ type: 'failure',payload: { error } })
      }))
    }
  }
}
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
const store = createStore(reducer, applyMiddleware(thunk))
const asyncAction = dispatch => {
  dispatch({ type: 'start' })
  fetch('/api').then(res => res.json).then(data => {
    dispatch({ type: 'success', payload: { data } })
  }).catch(error => {
    dispatch({ type: 'failure', payload: { error } })
  }))
}
store.dispatch(asyncAction())
import { createStore, applyMiddleware }from 'redux'
import promiseMiddleware from 'redux-promise'
import reducer from './reducer'
const stroe = createStore(reducer, applyMiddleware(promiseMiddleware))
const asyncAction = dispatch => fetch('/api').then(res =>res.json).then(data => {
  dispatch({ type: 'success', payload: { data } })
}).catch(error => {
dispatch({ type: 'failure', payload: { error } })
}))
store.dispatch({ type: 'start' })
store.dispatch(asyncAction())
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
function* asyncAction() {
 try{
    const data= yield call('/api')
    yield put({ type: 'success', payload: { data } })
  } catch(error){
    yield put({ type: 'failure', payload: { error } })
  }
}
function*mySaga(){ // 支持并发
  yield takeEvery('start', asyncAction)
}
function* mySage() { //不支持并发,前个处理中的相同 type 的 Action 会被取消
  yield takeLatest('start', asyncAction)
}
export default mySage

main.js

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'react-saga'
import reducer from './reducer'
import mySaga from './sagas'
const sagaMiddleware= createSagaMiddleware()
conststore = createStore(reducer, applyMiddleware(sagaMiddleware))
sagaMiddleware.run(mySaga)

如何判断项目需要引入 Redux ?

并非所有应用程序都需要 Redux,是否引入 Redux 由以下决定

Redux 可以共享和管理状态

平衡利弊,在以下情况引入 Redux 最有用

如何设置引入 Redux 后的项目目录?

小型项目

- components
- pages / containers
- actions
-reducers
-pages / containers
- HomePage
  - HomePage.jsx
  - HomePageAction.jsx
  -HomePageReducer.jsx

大型项目
提取公共组件,进一步拆分 actions 为同步 actions.js 和异步 sagas.js

- component #公共组件
-pages / containers
 - HomePage
  - component # 私有组件
  - index.jsx
  - reducer.js
  - action.js#同步 actions
- sagas.js  # 异步 actions
- .github # Github配置,包括 CI/CD工作流
- .vscode # VSCode 设置,包括代码片段、推荐的扩展等
- common # 公共(共享) React组件
- core # 核心模块和实用功能
 - store
   - createStore.js # 创建 sotre,允许注入reducer
    - reducers.js #默认或全局的 reducers
    - states.js #初始状态
- dialogs # 实现模态对话框的React组件
- fragments # 公共(共享)中继片段
- hooks # React 钩子,例如 useLocation(), useCurrentUser() 等
- icons # 自定义图标 React 组件
-menu # 实现弹出菜单的 React 组件
- public # 静态资源和页面,例如 robots.txt、index.html 等
- scripts # 自动化脚本,例如 yarn deploy npm
- theme # 应用程序主题、颜色、字体、内外边距等
- workers # Cloudflare Worker 或云计算、云函数、Lambda 脚本(反向代理、SSR)
- config# 每个环境的客户端应用程序设置
- index.js/ index.ts # 应用程序入口
- routes # 应用程序路由和页面(屏幕)组件
 - HomePage
   - component #私有组件
  - modules
    -reducer.js
     - action.js
   - index.jsx # 注入 modules/reducer 到 store

每个页面或容器组件 index.jsx 注入 modules/reducer 到 store

import { createStore, combineReducers} from 'redux'
import reducers as initReducersfrom './reducers'
importstatesas initStates from './states'
const store = createStore(initReducers,initStates)
store.reducers = Object.create(null)
store.injectReducer =(key, reducer) =>{
  store.reducers[key] = reducer
  store.replaceReducer(combineReducers([
    ...initReducers,
    ...store.reducers[key]
  ])))
}
export default store
import store from '../../core/store/createStore'
import reducer from 'modules/reducer'
store.injectReducer('homepage', reducer)

手写一个 redux-thunk ?

redux-thunk 允许 dispatch一个函数,而不是返回 action 的动作创建器

源码如下

function createThunkMiddleware(extraArgument){
  return ({dispatch, getState }) =>next=>action=> {
if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument)
    }

    return next(action)
  }
}
const thunk = createThunkMiddleware()
thunk.withExtraArgument = createThunkMiddleware
export default thunk

Next.js

什么是 Next.js ?

Next.js 是一款用于生产环境的 React 框架

Next.js 提供生产环境所需的所有功能和最佳开发体验

Next.js 的特点

Next.js 预渲染有哪些形式?

默认情况下,Next.js 预渲染每个 页面,即为每个页面生成 HTML 文件,无需客户端 JavaScript 渲染。从而带来更好的性能和 SEO 效果

每个生成的 HTML 页面都与该页面所需的最少 JavaScript 代码关联。当浏览器加载一个页面时,其 JavaScript 代码将运行并使页面完全具有交互性

Next.js 预渲染有两种形式,主要是页面生成时机不同:

为什么 Next.js 要重新设计一套路由?

Next.js 的路由基于文件系统,每个 pages 目录下的组件都是一条路由

Next.js 的路由面向生产环境设计,简单直观,开箱即用,前后端统一管理,利于维护

Next.js 中获取数据有哪些方法?

如何在 Next.js 中为静态资源配置 CDN ?

const isProd = process.env.NODE_ENV === 'production'
module.exports = {
  // Use the CDN in production and localhost for development
  assetPrefix: isProd ? 'https://cdn.mydomain.com' : ''
}

AntD

你的项目为什么选择 AntD ?

特性

兼容环境

社区支持

AntD 如何实现固定表头时,列头和内容对不齐怎么办?

AntD 如何实现虚拟列表?