欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

react面试考点和重要知识复习(未完)

程序员文章站 2022-03-11 20:53:17
...
  • react组件如何通讯
  • JSX本质是什么
  • context是什么,有何用途
  • shouldComponentUpdate(SCU)的用途
  • 描述redux单向数据流
  • setState是同步还是异步
  • 基于React设计一个todoList(组件结构,redux state数据结构)

基础回顾

JSX

jsx可以让我们直接在js代码里书写html(前提要引入React),例如

const JSXDemo =  () =>  {
    const data = {key: 'value'}
    return (
        <>
            <div>this is jsx</div>
            <div>key is {data.key}</div>
        </>
    )
}

条件渲染

类似javascript,可以有if else、三元表达式、短路表达式等 条件渲染

const ConditionDemo =  () => {
    const theme = 'black'
    const blackBtn = <button className={style.blackBtn}>black btn</button>
    const whiteBtn = <button className={style.whiteBtn}>white btn</button>
    // if else 
    if(theme === 'black') {
        return whiteBtn
    } else {
        return blackBtn
    }
    // 在jsx中使用三元表达式
    return (
        <>
            {
                theme === 'black' ? blackBtn : whiteBtn
            } 
        </>
    )
    // jsx中使用短路表达式
    return (
        <>
         {theme === 'black' && blackBtn}
        </>
    )
}

列表渲染

在jsx中渲染list,直接将含有jsx的数组进行渲染即可,例如

const ListDemo =  () => {
    const originList = [
        {
            id: 'id-1',
            title: '标题1'
        },
        {
            id: 'id-2',
            title: '标题2'
        },
        {
            id: 'id-3',
            title: '标题3'
        }
    ];
    const [list] = useState(originList)
    return (
        <>
            <ul>
            {/* arr.map产出一个jsx的数组即可在页面上渲染 */}
                {list.map((item, index) => <li key={item.id}>index: {index}; title: {item.title}</li>)}
            </ul>
        </>
    )
}

事件绑定

react的事件绑定中有this指向问题,不过这是真的class component来说的,默认的this是undefined。其中的event对象不是原生,而且在react中事件的target和currentTarget不同,后者是document,即react中所有的事件都挂载在document上。

export default class EventDemo extends React.Component {
    constructor(props) {
        super(props)
        // 这种效率最高,因为bind只执行一次
        this.handleClick2.bind(this)
    }
    handleClick1(arg) {
        console.log(arg)
    }
    handleClick2(e, arg) {
        console.log(e.target, arg)
    }
    handleClick3 = (e, args) => {
        console.log(e.target, args);
        // <butto> event 2</button> #document
        console.log(e.nativeEvent.target, e.nativeEvent.currentTarget);
    }
    render() {
        return (
            <>
                {/* 每次点击都会返回一个新的函数 */}
                <button onClick={this.handleClick1.bind(this, 'args')}>event 1</button>
                <button onClick={e => this.handleClick2(e, 'args')}> event 2</button>
                <button onClick={e => this.handleClick3(e, 'args')}>event 3</button>
            </>
        )
    }
}

综上得出:

  1. event是合成事件,模拟出DOM事件的全部功能
  2. event.nativeEvent是原生事件对象
  3. 所有的事件都挂载到了document上
  4. 和DOM事件不一样,和Vue事件也不一样

组件通讯

父子组件一般通过props进行通信(层级不深的时候)

import React, { useState } from 'react'
import PropTypes from 'prop-types'

const Input = ({ addListItem, length }) => {
    const [value, setValue] = useState('')
    const onInputChange = e => setValue(e.target.value)
    const submit = () => {
        if (value) {
            addListItem({ id: length + 1, title: value })
            setValue('')
        }
    }

    const onKeyUp = e => {
        if (e.which === 13) {
            submit()
        }
    }

    return (
        <>
            <input value={value} onChange={onInputChange} onKeyUp={onKeyUp} />
            <button onClick={submit}>提交</button>
        </>
    )
}

const List = ({ list, removeItem }) => {
    return <ul>{list.map((item, index) => <li onClick={() => removeItem(item.id)} key={item.id}><span>{index}.{item.title}</span></li>)}</ul>
}

List.propTypes = {
    list: PropTypes.array.isRequired,
    removeItem: PropTypes.func.isRequired
}

export default () => {
    const [list, setList] = useState([
        { id: 1, title: '标题1' }
    ])
    const addListItem = item => {
        const newList = [...list, item]
        setList(newList)
    }
    const removeItem = id => {
        const newList = list.filter(item => item.id !== id)
        setList(newList)
    }

    return (
        <>
            <Input addListItem={addListItem} length={list.length} />
            <List list={list} removeItem={removeItem} />
        </>
    )
}

关于useState中的set方法和class component中的setState,均为不可变值,只能通过调用setState等赋值新的值或地址,可能是异步更新,也可能会被合并(一次性多次set)
举个例子

export default class StateDemo extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
    }
    increase = () => {
        // setCount(c => c + 1)
        // console.log(count); // 这里拿不到最新的值
        // 多次设置,直接传入对象,合并为一次
        // const count = this.state.count
        // this.setState({ count: count + 1 })
        // this.setState({ count: count + 1 })
        // this.setState({ count: count + 1 })
        // 定时器中setState是同步的
        setTimeout(() => {
            this.setState({ count: this.state.count + 1 })
            console.log('count in setTimeout', this.state.count)
        }, 0);
        // 自己定义的DOM事件,setState是同步的
        // 见componentDidMound
        // 多次设置,传入函数时,不合并,同步执行
        // const count = this.state.count
        // this.setState((prevState, props) => ({ count: prevState.count + 1 }))
        // this.setState((prevState, props) => ({ count: prevState.count + 1 }))
        // this.setState((prevState, props) => ({ count: prevState.count + 1 }))

        // 同步
        // Promise.resolve().then(() => {
        //     this.setState({
        //         count: this.state.count + 1
        //     })
        //     console.log(this.state.count);
        // })
    }
    
    componentDidMount(){
        // 自己定义的DOM事件,setState是同步的
        document.body.addEventListener('click', () => {
            this.setState({count: this.state.count + 1})
            console.log(this.state.count)
        })
    }

    render() {
        return (
            <div>
                <p>{this.state.count}</p>
                <button onClick={this.increase}>累加</button>
            </div>
        )
    }
}

总的来说,setState是异步还是同步基于设置时的方式:
setState时位于基于event loop形式的回调函数里,则为同步,反之为异步(注意:如果setState传入函数时,是同步)

生命周期

单组件生命周期
react面试考点和重要知识复习(未完)
父子组件生命周期
和vue类似:

  1. 创建阶段:父 -> 子
  2. 渲染阶段:componentWillMount 父 -> 子, componentDidMount 子 -> 父
  3. 更新阶段:父组件更新数据,子组件更新数据;componentDidUpdate 子 -> 父
  4. 卸载阶段:componentDidUnMount 子 -> 父

高级特性

函数组件

函数组件不需要去理解class的生命周期,是一个纯函数,输入一个props,输出jsx。函数组件

import React, { Fragment, useState, useCallback, useEffect, useContext } from 'react'

// redux中还有useSelector useDispatch
// react-router还有useLocation、useHistory等hooks
export default () => {
    return (
        <Fragment>
        </Fragment>
    )
}

react函数组件可以使用hook,来管理数据和上下文等。这些后面介绍

非受控组件

非受控组件使用场景:

  1. 必须手动操作dom元素,setState实现不了
  2. 文件上传
  3. 某些富文本编辑器,需要传入dom元素

优先使用受控组件,必须操作DOM时,使用非受控组件

Portals(传送门)

有的时候,需要将子组件渲染到父组件外部(例如模态框、弹出层等),这个时候就需要使用Portals

import React, { Fragment } from 'react'
import ReactDOM from 'react-dom'
import style from './index.module.css'


export default ({ children }) => {
    // 将子组件渲染到body,避免弹出层嵌套层级过深
    return ReactDOM.createPortal(<div className={style.modal}>{children}</div>, document.body)
    // return (
    //     <Fragment>
    //         <div className={style.modal}>
    //             {children}
    //         </div>
    //     </Fragment>
    // )
}

context

context一般用于跨组件通讯,如果用redux的话小题大做。context有关的api有:useContext、createContext,一般和useReducer配合来完成类似redux的数据管理

import React, { Fragment, createContext, useContext, useReducer } from 'react'

const defualtTheme = 'light'
const THEME = 'THEME'
const themeCtx = createContext(defualtTheme)

function ThemeReducer(state, action) {
    switch (action.type) {
        case THEME:
            // 因为reducer是纯函数,所以每次数据的改动都必须返回一个新的对象
            return { ...state, theme: action.payload }

        default:
            return state
    }
}

const Link = () => {
    // 子组件获取数据
    const { value } = useContext(themeCtx)

    return (
        <div>{value.theme}</div>
    )
}

export default () => {
    const [reducer, dispatch] = useReducer(ThemeReducer, { theme: defualtTheme })
    const changeTheme = () => {
        dispatch({ type: THEME, payload: 'dark' })
    }
    return (
        <Fragment>
            {/* 传递数据到子组件 */}
            <themeCtx.Provider value={{ dispatch, value: reducer }}>
                <Link />
                <button onClick={changeTheme}>切换主题</button>
            </themeCtx.Provider>
        </Fragment>
    )
}

异步组件

和Vue一样,React中也是通过import()进行动态加载组件的,不过这个不常用,最常用的是React.lazy配个React.Suspense配合使用

import React, { Fragment } from 'react'


// import() 方式
// const load = () => import('./ContextDemo')
// export default () => {

//     const loadComponent = () => {
//         load()
//     }
//     return (
//         <Fragment>
//             <button onClick={loadComponent}>点击加载组件</button>
//         </Fragment>
//     )
// }

// 异步加载组件React.lazy和React.suspense
// function showImport(fn, delay) {
//     return new Promise(resolve => {
//         setTimeout(() => resolve(fn), delay)
//     })
// }

const Component = React.lazy(() => import('./ContextDemo.js'))

export default () => {
    return (
        <React.Suspense fallback="加载中....">
            <Component />
        </React.Suspense>
    )
}

性能优化

React中父组件更新,子组件无条件也更新;SCU不建议使用深度比较

  1. shouldComponentUpdate for class component, useEffect for functional component(类组件使用SCU进行优化,函数组件使用useEffect)
  2. PureComponent(浅层比较,可使用immutable.js进行控制)、React.memo(hook)
  3. 使用immutable.js

问题:

  1. 为什么SCU默认返回true,而要提供PureComponent供开发人员使用,为什么不默认就是PureComponent的形式?
    因为SCU只有在需要的时候需要,将优化的权力交给开发;

高阶组件HOC

Render Props