react面试考点和重要知识复习

  • 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(count + 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. 文件上传 <input type="file">
  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只有在需要的时候需要,将优化的权力交给开发;
组件抽离
  1. mixin,已被react废弃
  2. 高阶组件HOC
  3. Render Props
  4. useHook
高阶组件HOC

高阶组件并非是一种功能,而是一种模式。相当于高阶函数(或装饰器)。在redux中,connect就是一个高阶组件

import React, { Component } from 'react'

// 高阶组件,
const withComponent = (Comp) => {
    return class withComponent extends Component {
        constructor(props) {
            super(props)
            this.state = {
                x: 0,
                y: 0
            }
        }

        handleMouseMove = e => {

            this.setState({ x: e.clientX, y: e.clientY })
        }

        render() {
            return (
                <div
                    onMouseMove={this.handleMouseMove}
                    style={{ height: 500, width: 500, background: '#ccc', color: '#fff' }}>
                    <Comp mousePosition={this.state} {...this.props} />
                </div>
            )
        }
    }

}

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {}
    }
    render() {
        return (<div>x:{this.props.mousePosition.x}, x:{this.props.mousePosition.y}, </div>);
    }
}

export default withComponent(App)
Render Props

Render Props通过一个函数将class的state作为props传递给纯函数组件,与高阶组件相反,它是将内部的state传递到外部进行渲染。

import React, { Component } from 'react'
import PropTypes from 'prop-types'
class Factory extends Component {
    constructor(props) {
        super(props);
        this.state = {
            x: 0,
            y: 0
        }
    }
    handleMouseMove = e => {
        this.setState({ x: e.clientX, y: e.clientY })
    }

    render() {
        return (<div onMouseMove={this.handleMouseMove}>{this.props.render(this.state)}</div>);
    }
}

Factory.prototypes = {
    render: PropTypes.func.isRequired
}


const App = () => {
    return (
        <Factory render={props => <div style={{ height: 500, width: 500, background: '#ccc', color: '#fff' }}>x: {props.x}, y:{props.y}</div>}></Factory>
    )
}

export default App

HOC vs Render Props:

  1. HOC: 模式简单,但是会增加组件嵌套层级,代码比较难看懂
  2. Render Props:代码简洁,学习成本较高

Redux

Redux是一个javascript的数据或状态state管理框架,并不但用于react,但是却完美结合了react关于不可变值和纯函数的理念。

基本概念

store、action、reducer

单向数据流

概述:dispatch(action) -> reducer -> newState -> subscribe触发订阅 -> 更新视图

中间件

react-redux:Provider、connect、mapStateToProps、mapDispatchToProps、useSelector、useDispatch、useStore

异步action

使用redux-thunk

react-router

// Router.js
import React, { Fragment } from 'react'
import Home from './Home'
import NotFound from './NotFound'
import Project from './Project'
import { Route, HashRouter, Switch, Redirect } from 'react-router-dom'

export default () => {
    return (
        <Fragment>
            <HashRouter>
                <Switch>
                    {/* 匹配路由 */}
                    <Route exact path="/" component={Home} />
                    <Route path="/project/:id" component={Project} />
                    <Route path="/notfound" component={NotFound} />
                    <Redirect from="*" to='/notfound' />
                </Switch>
            </HashRouter>
        </Fragment>
    )
}

// Project.js
import React, { Fragment } from 'react'
import { useParams, useHistory } from 'react-router-dom'


export default () => {
    const params = useParams()
    const history = useHistory()
    return (
        <Fragment>
            this is Project, project id is {params.id}
            <button onClick={() => history.push('/')}>go to Home</button>
        </Fragment>
    )
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值