React 知识点总结

202 阅读14分钟

一、React 基本使用

1、JSX 的基本使用

变量、表达式

// 获取变量 插值
const pElem = <p>{this.state.name}</p>
return pElem

// 表达式
const exprElem = <p>{this.state.flag ? 'yes' : 'no'}</p>
return exprElem

class style

// class
const classElem = <p className="title">设置 css class</p>
return classElem

// style
const styleData = { fontSize: '30px',  color: 'blue' }
const styleElem = <p style={styleData}>设置 style</p>
// 内联写法,注意 {{ 和 }}
// const styleElem = <p style={{ fontSize: '30px',  color: 'blue' }}>设置 style</p>
return styleElem

子元素和组件

// 子元素
const imgElem = <div>
    <p>我的头像</p>
    <img src="xxxx.png"/>
    <img src={this.state.imgUrl}/>
</div>
return imgElem

// 加载组件
const componentElem = <div>
    <p>JSX 中加载一个组件</p>
    <hr/>
    <List/>
</div>
return componentElem
// 原生 html
const rawHtml = '<span>富文本内容<i>斜体</i><b>加粗</b></span>'
const rawHtmlData = {
    __html: rawHtml // 注意,必须是这种格式
}
const rawHtmlElem = <div>
    <p dangerouslySetInnerHTML={rawHtmlData}></p>
    <p>{rawHtml}</p>
</div>
return rawHtmlElem

条件判断

if-else、 三元表达式、&&

import React from 'react'
import './style.css'

class ConditionDemo extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            theme: 'black'
        }
    }
    render() {
        const blackBtn = <button className="btn-black">black btn</button>
        const whiteBtn = <button className="btn-white">white btn</button>

        // // if else
        // if (this.state.theme === 'black') {
        //     return blackBtn
        // } else {
        //     return whiteBtn
        // }

        // // 三元运算符
        // return <div>
        //     { this.state.theme === 'black' ? blackBtn : whiteBtn }
        // </div>

        // &&
        return <div>
            { this.state.theme === 'black' && blackBtn }
        </div>
    }
}

export default ConditionDemo

渲染列表

import React from 'react'

class ListDemo extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            list: [
                {
                    id: 'id-1',
                    title: '标题1'
                },
                {
                    id: 'id-2',
                    title: '标题2'
                },
                {
                    id: 'id-3',
                    title: '标题3'
                }
            ]
        }
    }
    render() {
        return <ul>
            { /* vue v-for */
                this.state.list.map(
                    (item, index) => {
                        // 这里的 key 和 Vue 的 key 类似,必填,不能是 index 或 random
                        return <li key={item.id}>
                            index {index}; id {item.id}; title {item.title}
                        </li>
                    }
                )
            }
        </ul>
    }
}

export default ListDemo

2、事件(重)

bind this

import React from 'react'

class EventDemo extends React.Component {
    constructor(props) {
        super(props)

        // 修改方法的 this 指向
        this.clickHandler1 = this.clickHandler1.bind(this)
    }
    render() {
        // this - 使用 bind
        return <p onClick={this.clickHandler1}>
            {this.state.name}
        </p>
     }
     
    clickHandler1() {
        // console.log('this....', this) // this 默认是 undefined
        this.setState({
            name: 'lisi'
        })
    }
    // 静态方法,this 指向当前实例
    clickHandler2 = () => {
        this.setState({
            name: 'lisi'
        })
    }
  
export default EventDemo

event 传参 ( 合成事件 )

import React from 'react'

class EventDemo extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {

        // event
        return <a href="https://imooc.com/" onClick={this.clickHandler3}>
            click me
        </a>
        
    }
    
    // 获取 event
    clickHandler3 = (event) => {
        event.preventDefault() // 阻止默认行为
        event.stopPropagation() // 阻止冒泡
        console.log('target', event.target) // 指向当前元素,即当前元素触发
        console.log('current target', event.currentTarget) // 指向当前元素,假象!!!

        // 注意,event 其实是 React 封装的。可以看 __proto__.constructor 是 SyntheticEvent 组合事件
        console.log('event', event) // 不是原生的 Event ,原生的 MouseEvent
        console.log('event.__proto__.constructor', event.__proto__.constructor)

        // 原生 event 如下。其 __proto__.constructor 是 MouseEvent
        console.log('nativeEvent', event.nativeEvent)
        console.log('nativeEvent target', event.nativeEvent.target)  // 指向当前元素,即当前元素触发
        console.log('nativeEvent current target', event.nativeEvent.currentTarget) // 指向 document !!!
    }
}

export default EventDemo

image.png

注意,event 其实是 React 封装的。可以看 __proto__.constructor SyntheticEvent 组合事件

  1. React 中的 eventSyntheticEvent(合成事件) ,模拟出来 DOM 事件所有能力

  2. event.nativeEvent 是原生事件对象

  3. 所有的事件,都被挂载到 document

  4. DOM 事件不一样,和 Vue 事件也不一样

传递自定义参数

import React from 'react'

class EventDemo extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: 'zhangsan',
            list: [
                {
                    id: 'id-1',
                    title: '标题1'
                },
                {
                    id: 'id-2',
                    title: '标题2'
                },
                {
                    id: 'id-3',
                    title: '标题3'
                }
            ]
        }
    }
    render() {

        // 传递参数 - 用 bind(this, a, b)
        return <ul>{this.state.list.map((item, index) => {
            return <li key={item.id} onClick={this.clickHandler4.bind(this, item.id, item.title)}>
                index {index}; title {item.title}
            </li>
        })}</ul>
    }
   
    // 传递参数
    clickHandler4(id, title, event) {
        console.log(id, title)
        console.log('event', event) // 最后追加一个参数,即可接收 event
    }
}

export default EventDemo

3、表单

image.png

受控组件

 input 值的来源是受  react 的  this.state 控制,属于受控组件

import React from 'react'

class FormDemo extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: '双越',
            info: '个人信息',
            city: 'beijing',
            flag: true,
            gender: 'male'
        }
    }
    render() {

        // 受控组件(非受控组件,后面再讲)
        return <div>
            <p>{this.state.name}</p>
            <label htmlFor="inputName">姓名:</label> {/* 用 htmlFor 代替 for */}
            <input id="inputName" value={this.state.name} onChange={this.onInputChange}/>
        </div>

        // textarea - 使用 value
        return <div>
            <textarea value={this.state.info} onChange={this.onTextareaChange}/>
            <p>{this.state.info}</p>
        </div>

        // select - 使用 value
        return <div>
            <select value={this.state.city} onChange={this.onSelectChange}>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="shenzhen">深圳</option>
            </select>
            <p>{this.state.city}</p>
        </div>

        // checkbox
        return <div>
            <input type="checkbox" checked={this.state.flag} onChange={this.onCheckboxChange}/>
            <p>{this.state.flag.toString()}</p>
        </div>

        // radio
        return <div>
            male <input type="radio" name="gender" value="male" checked={this.state.gender === 'male'} onChange={this.onRadioChange}/>
            female <input type="radio" name="gender" value="female" checked={this.state.gender === 'female'} onChange={this.onRadioChange}/>
            <p>{this.state.gender}</p>
        </div>

        // 非受控组件 - 后面再讲
    }
    onInputChange = (e) => {
        this.setState({
            name: e.target.value
        })
    }
    onTextareaChange = (e) => {
        this.setState({
            info: e.target.value
        })
    }
    onSelectChange = (e) => {
        this.setState({
            city: e.target.value
        })
    }
    onCheckboxChange = () => {
        this.setState({
            flag: !this.state.flag
        })
    }
    onRadioChange = (e) => {
        this.setState({
            gender: e.target.value
        })
    }
}

export default FormDemo

4、父子组件传值

image.png

方法

父组件向子组件可以传递对象也可以传递一个方法,子组件通过const {obj, fn} = this.props获取传递的值或者方法。

类型检查 PropTypes

什么是prop-typesprop代表父组件传递过来的值,types代表类型。简单来说就是用来校验父组件传递过来值的类型

propTypes用来规范props必须满足的类型,如果验证不通过将会有warn提示。

首先安装 prop-type,然后引入并使用:

import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};

Demo

/**
 * @description 演示 props 和事件
 * @author 双越老师
 */

import React from 'react'
import PropTypes from 'prop-types'

class Input extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            title: ''
        }
    }
    render() {
        return <div>
            <input value={this.state.title} onChange={this.onTitleChange}/>
            <button onClick={this.onSubmit}>提交</button>
        </div>
    }
    onTitleChange = (e) => {
        this.setState({
            title: e.target.value
        })
    }
    onSubmit = () => {
         // props 传递函数
        const { submitTitle } = this.props
        // 子组件直接调用 父组件 传递的函数
        submitTitle(this.state.title) // 'abc'

        this.setState({
            title: ''
        })
    }
}
// props 类型检查
Input.propTypes = {
    submitTitle: PropTypes.func.isRequired
}

class List extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
         // props 传递属性
        const { list } = this.props

        return <ul>{list.map((item, index) => {
            return <li key={item.id}>
                <span>{item.title}</span>
            </li>
        })}</ul>
    }
}
// props 类型检查
List.propTypes = {
    list: PropTypes.arrayOf(PropTypes.object).isRequired
}

class TodoListDemo extends React.Component {
    constructor(props) {
        super(props)
        // 状态(数据)提升,父组件统一管理状态,子组件负责显示
        this.state = {
            list: [
                {
                    id: 'id-1',
                    title: '标题1'
                },
                {
                    id: 'id-2',
                    title: '标题2'
                },
                {
                    id: 'id-3',
                    title: '标题3'
                }
            ]
        }
    }
    render() {
        return <div>
            // 向子组件传递方法
            <Input submitTitle={this.onSubmitTitle}/>
            
            // 向子组件传递属性
            <List list={this.state.list}/>

        </div>
    }
    onSubmitTitle = (title) => {
        this.setState({
            list: this.state.list.concat({
                id: `id-${Date.now()}`,
                title
            })
        })
    }
}

export default TodoListDemo

5、setState 为何使用不可变值(重要)

不可变值

不要直接修改 state ,原因是 shouldComponentUpdate, 后面讲。

值类型

// this.state.count++ // 错误
this.setState({
    count: this.state.count + 1 // SCU
})

操作数组的常用形式

注意,不能直接对 this.state.list 进行 push pop splice 等,这样违反不可变值

// 不可变值(函数式编程,纯函数) - 数组
const list5Copy = this.state.list5.slice()
list5Copy.splice(2, 0, 'a') // 中间插入/删除
this.setState({
    list1: this.state.list1.concat(100), // 追加
    list2: [...this.state.list2, 100], // 追加
    list3: this.state.list3.slice(0, 3), // 截取
    list4: this.state.list4.filter(item => item > 100), // 筛选
    list5: list5Copy // 其他操作
})

对象

注意,不能直接对 this.state.obj 进行属性设置,这样违反不可变值

// 不可变值 - 对象
this.setState({
    obj1: Object.assign({}, this.state.obj1, {a: 100}),
    obj2: {...this.state.obj2, a: 100}
})

可能是异步更新

1、setState 可能是异步更新(有可能是同步更新)

this.setState({
    count: this.state.count + 1
}, () => {
    // 联想 Vue $nextTick - DOM
    console.log('count by callback', this.state.count) // 回调函数中可以拿到最新的 state
})
console.log('count', this.state.count) // 异步的,拿不到最新值

image.png

2、setTimeout setState 是同步的

setTimeout(() => {
    this.setState({
        count: this.state.count + 1
    })
    console.log('count in setTimeout', this.state.count)
}, 0)

image.png

3、 自己定义的DOM事件,setState 是同步的。

componentDidMount() {
    // 自己定义的 DOM 事件,setState 是同步的
    document.body.addEventListener('click', this.bodyClickHandler)
}

bodyClickHandler = () => {
    this.setState({
        count: this.state.count + 1
    })
    console.log('count in body event', this.state.count)
}

componentWillUnmount() {
    // 及时销毁自定义 DOM 事件
    document.body.removeEventListener('click', this.bodyClickHandler)
    // clearTimeout
}

可能会被合并

setState中传入对象就会被合并,state 异步更新的话,更新前会被合并

// 传入对象,会被合并(类似 Object.assign )。执行结果只执行一次 +1
this.setState({
    count: this.state.count + 1
})
this.setState({
    count: this.state.count + 1
})
this.setState({
    count: this.state.count + 1
})

serState 传入函数,不会被合并。执行结果是 +3

this.setState((prevState, props) => {
    return {
        count: prevState.count + 1
    }
})
this.setState((prevState, props) => {
    return {
        count: prevState.count + 1
    }
})
this.setState((prevState, props) => {
    return {
        count: prevState.count + 1
    }
})

6、React 生命周期

每个组件都包含 “生命周期方法”,你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。

挂载阶段: 当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

constructor(): 在 React 组件挂载之前,会调用它的构造函数。

static getDerivedStateFromProps(): 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

render(): 方法是 class 组件中唯一必须实现的方法。render() 函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。

componentDidMount(): 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。

更新阶段: 当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

static getDerivedStateFromProps(): 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

shouldComponentUpdate(nextProps, nextState):

render():

getSnapshotBeforeUpdate()

componentDidUpdate(): 会在更新后会被立即调用。首次渲染不会执行此方法。

卸载阶段: 当组件从 DOM 中移除时会调用如下方法:

componentWillUnmount(): 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

image.png

image.png image.png

二、React 高级特性

1、函数组件和class组件有什么区别?

image.png

image.png

2、什么是react非受控组件

定义

受控组件类似于vue 的双向数据绑定,数据和视图绑定在一起,非受控组件只是从dom中得到对应的数据(是单向的)

image.png

使用

1、react 创建 ref:

const nameRef = React.createRef()

2、非受控组件input使用defaultValue绑值,设置ref

// 非受控组件
return <div>
    <input defaultValue={this.state.name} ref={this.nameRef}/>
    <span>state.name: {this.state.name}</span>
    <button onClick={this.alertName}>alert name</button>
</div>

3、使用this.nameRef.current.value获取值,获取的是DOM节点的值,没有响应式

alertName = () => {
    console.log('state: ', this.state.name)
    console.log('input name: ', this.inputRef.current.value)
}

Demo

import React from 'react'

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: '双越',
            flag: true,
        }
        this.nameInputRef = React.createRef() // 创建 ref
        this.fileInputRef = React.createRef()
    }
    render() {
        // // input defaultValue
        // return <div>
        //     {/* 使用 defaultValue 而不是 value ,使用 ref */}
        //     <input defaultValue={this.state.name} ref={this.nameInputRef}/>
        //     {/* state 并不会随着改变 */}
        //     <span>state.name: {this.state.name}</span>
        //     <br/>
        //     <button onClick={this.alertName}>alert name</button>
        // </div>

        // // checkbox defaultChecked
        // return <div>
        //     <input
        //         type="checkbox"
        //         defaultChecked={this.state.flag}
        //     />
        // </div>

        // file
        return <div>
            <input type="file" ref={this.fileInputRef}/>
            <button onClick={this.alertFile}>alert file</button>
        </div>

    }
    alertName = () => {
        const elem = this.nameInputRef.current // 通过 ref 获取 DOM 节点
        alert(elem.value) // 不是 this.state.name
    }
    alertFile = () => {
        const elem = this.fileInputRef.current // 通过 ref 获取 DOM 节点
        alert(elem.files[0].name)
    }
}

export default App

使用场景

image.png

受控组件 VS 非受控组件

image.png

3、什么场景需要 React Portals

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

ReactDOM.createPortal(child, container)

用法

通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:

image.png

import React from 'react'
import ReactDOM from 'react-dom'
import './style.css'

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
        }
    }
    render() {
        // // 正常渲染
        // return <div className="modal">
        //     {this.props.children} {/* vue slot */}
        // </div>

        // 使用 Portals 渲染到 body 上。
        // fixed 元素要放在 body 上,有更好的浏览器兼容性。
        return ReactDOM.createPortal(
            <div className="modal">{this.props.children}</div>,
            document.body // DOM 节点
        )
    }
}

export default App

style.css

.modal {
    position: fixed;
    width: 300px;
    height: 100px;
    top: 100px;
    left: 50%;
    margin-left: -150px;
    background-color: #000;
    /* opacity: .2; */
    color: #fff;
    text-align: center;
}

放在了body的第一层:

image.png

使用场景

image.png

4、是否用过 react context

作用

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props

image.png

两个重点:

一个是使用场景: 一些公共信息如:修改主题、修改语言等;

一个是如何使用context

使用方法

1、创建Context

填入默认值(任何一个JS变量)

const ThemeContext = React.createContext('light')

2、管理和生产context

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            theme: 'light'
        }
    }
    render() {
        return <ThemeContext.Provider value={this.state.theme}>
            <Toolbar />
            <hr/>
            <button onClick={this.changeTheme}>change theme</button>
        </ThemeContext.Provider>
    }
    changeTheme = () => {
        this.setState({
            theme: this.state.theme === 'light' ? 'dark' : 'light'
        })
    }
}

export default App

3、消费context

函数组件使用

函数组件实例,就没有 this,所以不能使用 this.context,可以使用 ThemeContext.Consumer

// 底层组件 - 函数是组件
function ThemeLink (props) {
    // const theme = this.context // 会报错。函数式组件没有实例,即没有 this

    console.log('themeLink', props)
    // 函数式组件可以使用 Consumer
    return <ThemeContext.Consumer>
        { value => <p>link's theme is {value}</p> }
    </ThemeContext.Consumer>
}
class组件使用
// 指定 contextType 读取当前的 theme context。
ThemedButton.contextType = ThemeContext 

// 底层组件 - class 组件
class ThemedButton extends React.Component {
    // 指定 contextType 读取当前的 theme context。
    // static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
    render() {
        const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。
        return <div>
            <p>button's theme is {theme}</p>
        </div>
    }
}

demo

import React from 'react'

// 创建 Context 填入默认值(任何一个 js 变量)
const ThemeContext = React.createContext('light')

// 底层组件 - 函数是组件
function ThemeLink (props) {
    // const theme = this.context // 会报错。函数式组件没有实例,即没有 this

    console.log('themeLink', props)
    // 函数式组件可以使用 Consumer
    return <ThemeContext.Consumer>
        { value => <p>link's theme is {value}</p> }
    </ThemeContext.Consumer>
}

// 底层组件 - class 组件
class ThemedButton extends React.Component {
    // 指定 contextType 读取当前的 theme context。
    // static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
    render() {
        const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。
        return <div>
            <p>button's theme is {theme}</p>
        </div>
    }
}
// 指定 contextType 读取当前的 theme context。
ThemedButton.contextType = ThemeContext 

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
    return (
        <div>
            <ThemedButton />
            <ThemeLink />
        </div>
    )
}

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            theme: 'light'
        }
    }
    render() {
        return <ThemeContext.Provider value={this.state.theme}>
            <Toolbar />
            <hr/>
            <button onClick={this.changeTheme}>change theme</button>
        </ThemeContext.Provider>
    }
    changeTheme = () => {
        this.setState({
            theme: this.state.theme === 'light' ? 'dark' : 'light'
        })
    }
}

export default App

5、react 如何如何异步加载组件

image.png

Suspense 和 React.lazy()

Suspense 使得组件可以“等待”某些操作结束后,再进行渲染。目前,Suspense 仅支持的使用场景是:通过 React.lazy 动态加载组件

React.lazy() 允许你定义一个动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。

// 这个组件是动态加载的
const SomeComponent = React.lazy(() => import('./SomeComponent'));

demo

import React from 'react'

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

class App extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return <div>
            <p>引入一个动态组件</p>
            <hr />
            <React.Suspense fallback={<div>Loading...</div>}>
                <ContextDemo/>
            </React.Suspense>
        </div>

        // 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
        // 2. 看 network 的 js 加载
    }
}

export default App

6、性能优化

image.png

image.png

shouldComponentUpdate(简称 SCU)核心问题在哪里?

用法(SCU默认返回什么?)

接收两个参数,一个是nextProps, nextState,通过对比两个参数的值,如果相等就返回 false,不重复渲染;如果不等就返回true ,需要重新渲染。默认值返回true,可以进行性能优化。

image.png

SCU 背后的逻辑原理

react 默认 父组件更新,子组件也会无条件更新,因此,性能优化对于react 更重要。

SCU 每次都需要使用吗?不一定,性能优化,只是在需要的时候才使用

SCU 配合不可变值

错误写法解析:

如果数组使用push()等修改原数组的方法,那么修改前和修改后两者的引用地址是相同的. image.png

再进行SCU比较的时候,前一个props 和 后一个 props 的值是完全一样的,所以不会重新渲染,导致页面不刷新的bug.

_.isEqual()是深拷贝方法,比较耗费性能,(一次性递归到底);会触发props的值的一次性递归到底。可以设置props的值层级不要太深,实现浅比较。 image.png

总结

image.png

Demo

import React from 'react'

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
    }
    render() {
        return <div>
            <span>{this.state.count}</span>
            <button onClick={this.onIncrease}>increase</button>
        </div>
    }
    onIncrease = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    // 演示 shouldComponentUpdate 的基本使用
    shouldComponentUpdate(nextProps, nextState) {
        if (nextState.count !== this.state.count) {
            return true // 可以渲染
        }
        return false // 不重复渲染
    }
}

export default App
import React from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'

class Input extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            title: ''
        }
    }
    render() {
        return <div>
            <input value={this.state.title} onChange={this.onTitleChange}/>
            <button onClick={this.onSubmit}>提交</button>
        </div>
    }
    onTitleChange = (e) => {
        this.setState({
            title: e.target.value
        })
    }
    onSubmit = () => {
        const { submitTitle } = this.props
        submitTitle(this.state.title)

        this.setState({
            title: ''
        })
    }
}
// props 类型检查
Input.propTypes = {
    submitTitle: PropTypes.func.isRequired
}

class List extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        const { list } = this.props

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

    // 增加 shouldComponentUpdate
    shouldComponentUpdate(nextProps, nextState) {
        // _.isEqual 做对象或者数组的深度比较(一次性递归到底)
        if (_.isEqual(nextProps.list, this.props.list)) {
            // 相等,则不重复渲染
            return false
        }
        return true // 不相等,则渲染
    }
}
// props 类型检查
List.propTypes = {
    list: PropTypes.arrayOf(PropTypes.object).isRequired
}

class TodoListDemo extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            list: [
                {
                    id: 'id-1',
                    title: '标题1'
                },
                {
                    id: 'id-2',
                    title: '标题2'
                },
                {
                    id: 'id-3',
                    title: '标题3'
                }
            ]
        }
    }
    render() {
        return <div>
            <Input submitTitle={this.onSubmitTitle}/>
            <List list={this.state.list}/>
        </div>
    }
    onSubmitTitle = (title) => {
        // 正确的用法
        this.setState({
            list: this.state.list.concat({
                id: `id-${Date.now()}`,
                title
            })
        })

        // // 为了演示 SCU ,故意写的错误用法,违反了 state 不可变值
        // this.state.list.push({
        //     id: `id-${Date.now()}`,
        //     title
        // })
        // this.setState({
        //     list: this.state.list
        // })
    }
}

export default TodoListDemo

PureComponent( 纯组件 )

大部分情况下,你可以使用 React.PureComponent 来代替手写 shouldComponentUpdate。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。 image.png

memo

image.png

注意:

与 class 组件中 shouldComponentUpdate() 方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。

不可变值 immutable.js

image.png

image.png

7、高阶组件HOC

高阶组件(HOC)React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

组件公共逻辑的抽离

image.png

image.png

demo案例:

import React from 'react'

// 高阶组件
const withMouse = (Component) => {
    class withMouseComponent extends React.Component {
        constructor(props) {
            super(props)
            this.state = { x: 0, y: 0 }
        }
  
        handleMouseMove = (event) => {
            this.setState({
                x: event.clientX,
                y: event.clientY
            })
        }
  
        render() {
            return (
                <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
                    {/* 1. 透传所有 props 2. 增加 mouse 属性 */}
                    <Component {...this.props} mouse={this.state}/>
                </div>
            )
        }
    }
    return withMouseComponent
}

const App = (props) => {
    const a = props.a
    const { x, y } = props.mouse // 接收 mouse 属性
    return (
        <div style={{ height: '500px' }}>
            <h1>The mouse position is ({x}, {y})</h1>
            <p>{a}</p>
        </div>
    )
}

export default withMouse(App) // 返回高阶函数

image.png

connect 源码: image.png

8、render props

术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术

image.png

demo

import React from 'react'
import PropTypes from 'prop-types'

class Mouse extends React.Component {
    constructor(props) {
        super(props)
        this.state = { x: 0, y: 0 }
    }
  
    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }
  
    render() {
      return (
        <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
            {/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
            {this.props.render(this.state)}
        </div>
      )
    }
}
Mouse.propTypes = {
    render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
}

const App = (props) => (
    <div style={{ height: '500px' }}>
        <p>{props.a}</p>
        <Mouse render={
            /* render 是一个函数组件 */
            ({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>
        }/>
        
    </div>
)

/**
 * 即,定义了 Mouse 组件,只有获取 x y 的能力。
 * 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
 */

export default App

HOC VS Render Props

image.png

三、周边工具

1、 Redux使用

image.png

基本概念

image.png

单项数据流

image.png

react-redux

image.png

异步 action

image.png image.png

image.png

中间件

image.png

image.png

image.png

image.png

2、react-router

image.png

image.png

image.png

image.png

image.png

image.png

image.png

四、React 原理

image.png

1、函数式编程

image.png

2、vdom 和 diff算法

image.png

image.png

image.png

3、JSX 的本质

image.png

// https://www.babeljs.cn/

// // JSX 基本用法
// const imgElem = <div id="div1">
//     <p>some text</p>
//     <img src={imgUrl}/>
// </div>

// // JSX style
// const styleData = { fontSize: '30px',  color: 'blue' }
// const styleElem = <p style={styleData}>设置 style</p>

// // JSX 加载组件
// const app = <div>
//     <Input submitTitle={onSubmitTitle}/>
//     <List list={list}/>
// </div>

// // JSX 事件
// const eventList = <p onClick={this.clickHandler}>
//     some text
// </p>

// // JSX list
// const listElem = <ul>{this.state.list.map((item, index) => {
//     return <li key={item.id}>index {index}; title {item.title}</li>
// })}</ul>


// // 总结
// React.createElement('div', null, [child1, child2, child3])
// React.createElement('div', {...}, child1, child2, child3)
// React.createElement(List, null, child1, child2, '文本节点')
// // h 函数
// // 返回 vnode
// // patch

image.png

image.png

4、合成事件

image.png

image.png

为何要用合成事件机制 ?

image.png

5、setState、 batchUpdate 机制

image.png

setState 主流程

image.png

image.png

setTimeout 回调函数:

image.png

自定义 DOM 事件: image.png

image.png

image.png

image.png

6、transaction 事务机制

image.png

image.png

image.png

7、组件渲染和更新的过程

image.png

image.png

image.png

image.png

组件渲染过程

image.png

组件更新过程

image.png

image.png

react-fiber 如何优化性能?

image.png

image.png

五、面试

1、组件之间如何通讯 ?

image.png

2、JSX的本质是什么 ?

image.png

3、context 是什么,如何应用 ?

image.png

4、shouldComponentUpdate 用途 ?

image.png

5、redux 单项数据流 ?

image.png

6、setStatus 场景题 ?

image.png

7、什么是纯函数 ?

image.png

8、React 组件生命周期 ?

image.png

9、React 发起 ajax 应该在那个生命周期 ?

image.png

10、渲染列表,为何使用 key ?

image.png

11、函数组件 和 class 组件的区别 ?

image.png

12、什么是受控组件 ?

image.png

13、为何使用异步组件 ?

image.png

14、多个组件有公共逻辑,如何抽离 ?

image.png

15、redux 如何进行异步请求?

image.png

16、react-router 如何配置懒加载 ?

image.png

17、PureComponent 有何区别 ?

image.png

18、 react 事件和 Dom事件的区别 ?

image.png

19、react 性能优化 ?

image.png

image.png

image.png

20、React 和 Vue 的区别 ?

image.png

image.png