中级-框架基础(中)

190 阅读19分钟

七、React使用

React基本使用

1.JSX基本使用

变量、表达式
class style
子元素和组件

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

class JSXBaseDemo extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: '双越',
            imgUrl: 'https://img1.mukewang.com/5a9fc8070001a82402060220-140-140.jpg',
            flag: true
        }
    }
    render() {
        // // 获取变量 插值
        // const pElem = <p>{this.state.name}</p>
        // return pElem

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

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

        // // 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

        // 原生 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

        // // 加载组件
        // const componentElem = <div>
        //     <p>JSX 中加载一个组件</p>
        //     <hr/>
        //     <List/>
        // </div>
        // return componentElem
    }
}

export default JSXBaseDemo

2.条件判断

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

3.渲染列表

map:map返回一个重组数组
key

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

4.事件(重点)

bind this
关于event参数(clickHandler3示例很重要)
传递自定义参数

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'
                }
            ]
        }

        // 修改方法的 this 指向,在这边bind初始化时一次就好,性能好.
        this.clickHandler1 = this.clickHandler1.bind(this)
        // 如果在下面使用:onClick={this.clickHandler1.bind(this)}就会在点击DOM上bind每次点击都要bind一次
    }
    render() {
        // // this - 使用 bind
        // return <p onClick={this.clickHandler1}>
        //     {this.state.name}
        // </p>

        // // this - 使用静态方法
        // return <p onClick={this.clickHandler2}>
        //     clickHandler2 {this.state.name}
        // </p>

        // // event
        // return <a href="https://imooc.com/" onClick={this.clickHandler3}>
        //     click me
        // </a>

        // 传递参数 - 用 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>
    }
    clickHandler1() {
        // console.log('this....', this) // this 默认是 undefined
        this.setState({
            name: 'lisi'
        })
    }
    // 静态方法,this 指向当前实例
    clickHandler2 = () => {
        this.setState({
            name: 'lisi'
        })
    }
    // 获取 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 !!!

        // 1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
        // 2. event.nativeEvent 是原生事件对象
        // 3. 所有的事件,都被挂载到 document 上
        // 4. 和 DOM 事件不一样,和 Vue 事件也不一样
    }
    // 传递参数
    clickHandler4(id, title, event) {
        console.log(id, title)
        console.log('event', event) // 最后追加一个参数,即可接收 event
    }
}

export default EventDemo

5.表单

受控组件:input的值受state影响
input textarea select 用valuei
checkbox radio 用 checked

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

6.组件使用

props传递数据
props传递函数
props类型检查

/**
 * @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 = () => {
        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() {
        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 Footer extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return <p>
            {this.props.text}
            {this.props.length}
        </p>
    }
    componentDidUpdate() {
        console.log('footer did update')
    }
    shouldComponentUpdate(nextProps, nextState) {
        if (nextProps.text !== this.props.text
            || nextProps.length !== this.props.length) {
            return true // 可以渲染
        }
        return false // 不重复渲染
    }

    // React 默认:父组件有更新,子组件则无条件也更新!!!
    // 性能优化对于 React 更加重要!
    // SCU 一定要每次都用吗?—— 需要的时候才优化
}

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'
                }
            ],
            footerInfo: '底部文字'
        }
    }
    render() {
        return <div>
            <Input submitTitle={this.onSubmitTitle}/>
            <List list={this.state.list}/>
            <Footer text={this.state.footerInfo} length={this.state.list.length}/>
        </div>
    }
    onSubmitTitle = (title) => {
        this.setState({
            list: this.state.list.concat({
                id: `id-${Date.now()}`,
                title
            })
        })
    }
}

export default TodoListDemo

7.setState(考察的重点--重中之重)

不可变值(setstate不能提前对state值进行修改,应该什么时候改就什么时候设值,而且设置的时候不能影响之前state的值)

可能是异步更新(直接使用是异步的,在setTimeout 和自定义DOM 事件中 是同步的)

可能会被合并

import React from 'react'

class ListDemo extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
    }
    render() {
        return <p>{this.state.count}</p>
    }
    componentDidMount() {
        // count 初始值为 0
        this.setState({ count: this.state.count + 1 })
        console.log('1', this.state.count) // 0
        this.setState({ count: this.state.count + 1 })
        console.log('2', this.state.count) // 0
        setTimeout(() => {
            this.setState({ count: this.state.count + 1 })
            console.log('3', this.state.count) // 2
        })
        setTimeout(() => {
            this.setState({ count: this.state.count + 1 })
            console.log('4', this.state.count) // 3
        })
    }
}

export default ListDemo
import React from 'react'

// 函数组件(后面会讲),默认没有 state
class StateDemo extends React.Component {
    constructor(props) {
        super(props)

        // 第一,state 要在构造函数中定义
        this.state = {
            count: 0
        }
    }
    render() {
        return <div>
            <p>{this.state.count}</p>
            <button onClick={this.increase}>累加</button>
        </div>
    }
    increase = () => {
        // // 第二,不要直接修改 state ,使用不可变值 ----------------------------
        // // this.state.count++ // 错误
        // this.setState({
        //     count: this.state.count + 1 // SCU
        // })
        // 操作数组、对象的的常用形式

        // 第三,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) // 异步的,拿不到最新值

        // // setTimeout 中 setState 是同步的
        // setTimeout(() => {
        //     this.setState({
        //         count: this.state.count + 1
        //     })
        //     console.log('count in setTimeout', this.state.count)
        // }, 0)

        // 自己定义的 DOM 事件,setState 是同步的。再 componentDidMount 中

        // 第四,state 异步更新的话,更新前会被合并 -------------------------
          // componentDidMount(){
            //  document.body.addEventListener('click',()=>{
               // this.setState({
              //          count:this.state.count+1
              //  })
            //  console.log(this.state.count,'count in body event')
         //   })
   //   }
        
        // // 传入对象,会被合并(类似 Object.assign )。执行结果只一次 +1
        // this.setState({
        //     count: this.state.count + 1
        // })
        // this.setState({
        //     count: this.state.count + 1
        // })
        // this.setState({
        //     count: this.state.count + 1
        // })
        
        // 传入函数,不会被合并。执行结果是 +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
            }
        })
    }
    // bodyClickHandler = () => {
    //     this.setState({
    //         count: this.state.count + 1
    //     })
    //     console.log('count in body event', this.state.count)
    // }
    // componentDidMount() {
    //     // 自己定义的 DOM 事件,setState 是同步的
    //     document.body.addEventListener('click', this.bodyClickHandler)
    // }
    // componentWillUnmount() {
    //     // 及时销毁自定义 DOM 事件
    //     document.body.removeEventListener('click', this.bodyClickHandler)
    //     // clearTimeout
    // }
}

export default StateDemo

// -------------------------- 我是分割线 -----------------------------

// // 不可变值(函数式编程,纯函数) - 数组
// 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.list 进行 push pop splice 等,这样违反不可变值

// // 不可变值 - 对象
// this.setState({
//     obj1: Object.assign({}, this.state.obj1, {a: 100}),
//     obj2: {...this.state.obj2, a: 100}
// })
// // 注意,不能直接对 this.state.obj 进行属性设置,这样违反不可变值

8.组件生命周期--必考

单组件生命周期

image.png image.png React 组件生命周期图示
projects.wojtekmaj.pl/react-lifec…

父子组件生命周期,和Vue的一样

React高级特性

1.函数组件

纯函数,输入props,输出JSX
没有实例,没有生命周期,没有state
不能扩展其他方法

image.png

2.非受控组件

ref
defaultValue defaultChecked
手动操作DOM元素

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

使用场景

必须手动操作DOM元素,setState实现不了
文件上传<input type = file>
某些富文本编辑器,需要传入DOM元素

受控组件 vs 非受控组件

优先使用受控组件,符合React设计原则
必须操作DOM时,再使用非受控组件

3.Portals 传送门

组件默认会按照既定层次嵌套渲染

如何让组件渲染到父组件以外?

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

image.png

使用场景

overflow:hidden(父组件bfc,限制子组件展示)

父组件z-index值太小(让子组件渲染逃离父组件)

fixed需要放在body第一层级(让子组件渲染逃离父组件)

4.context

公共信息(语言、主题)如何传递给每个组件
用props太繁琐
用redux小题大做

import React from 'react'

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

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

    // 函数式组件可以使用 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>
    }
}
ThemedButton.contextType = ThemeContext // 指定 contextType 读取当前的 theme context。

// 中间的组件再也不必指明往下传递 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.异步组件(懒加载)

import()
React.lazy
React.Suspense

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

强制刷新,可看到 loading (看不到就限制一下 chrome 网速)

image.png 看 network 的 js 加载,会单独有个js

image.png

6.性能优化(重点)

性能优化,永远都是面试的重点
性能优化对于React更加重要
回顾讲setState时重点强调的不可变值

shouldComponentUpdate(简称SCU)

image.png React默认:父组件有更新,子组件无论数据是否改变则无条件也更新
SCU默认返回true

/**
 * @description 演示 props 和事件
 * @author 双越老师
 // frame-project-interview/react-code-demo/src/components/baseUse/PropsDemo.js
 */

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 = () => {
        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() {
        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 Footer extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return <p>
            {this.props.text}
            {this.props.length}
        </p>
    }
    componentDidUpdate() {
        console.log('footer did update')
    }
    shouldComponentUpdate(nextProps, nextState) {
        if (nextProps.text !== this.props.text
            || nextProps.length !== this.props.length) {
            return true // 可以渲染
        }
        return false // 不重复渲染
    }

    // React 默认:父组件有更新,子组件则无条件也更新!!!
    // 性能优化对于 React 更加重要!
    // SCU 一定要每次都用吗?—— 需要的时候才优化
}

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'
                }
            ],
            footerInfo: '底部文字'
        }
    }
    render() {
        return <div>
            <Input submitTitle={this.onSubmitTitle}/>
            <List list={this.state.list}/>
            <Footer text={this.state.footerInfo} length={this.state.list.length}/>
        </div>
    }
    onSubmitTitle = (title) => {
        this.setState({
            list: this.state.list.concat({
                id: `id-${Date.now()}`,
                title
            })
        })
    }
}

export default TodoListDemo
SCU一定要配合不可变值
// react-code-demo/src/components/advancedUse/SCUDemo2.js
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 做对象或者数组的深度比较(一次性递归到底),深度比较较耗费性能,可以使用浅比较,设计state层级尽量设计得不要太深,扁平一些
        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 ,故意写的错误用法,会导致SCU不执行,因为state值push后和setState一致
        // this.state.list.push({
        //     id: `id-${Date.now()}`,
        //     title
        // })
        // this.setState({
        //     list: this.state.list
        // })
    }
}

export default TodoListDemo
SCU使用总结

SCU默认返回true,即React默认重新渲染所有子组件
必须配合“不可变值”一起使用
可先不用SCU,有性能问题时再考虑使用

PureComponent和React.memo

PureComponent,SCU中实现了浅比较
memo,函数组件中的PureComponent
浅比较已适用大部分情况(尽量不要深度比较)

//PureComponentDemo.js
//react-code-demo/src/components/advancedUse/PureComponentDemo.js
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 = () => {
        const { submitTitle } = this.props
        submitTitle(this.state.title)

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

class List extends React.PureComponent {
    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() {/*浅比较*/}
}
// 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 ,故意写的错误用法
        // this.state.list.push({
        //     id: `id-${Date.now()}`,
        //     title
        // })
        // this.setState({
        //     list: this.state.list
        // })
    }
}

export default TodoListDemo

image.png

不可变值immutable.js

彻底拥抱“不可变值”
基于共享数据(不是深拷贝),速度好
有一定学习和迁移成本,按需使用

image.png

7.高阶组件HOC

关于组件公共逻辑的抽离

mixin,已被React弃用
高阶组件HOC
Render Props

image.png

// react-code-demo/src/components/advancedUse/HOCDemo.js
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

提问:Vue如何实现高阶组件?

8.Renders Props

image.png

// react-code-demo/src/components/advancedUse/RenderPropDemo.js
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

HOC:模式简单,但会增加组件层级
Render Props:代码简洁,学习成本较高
按需使用

Redux

和Vuex作用相同,但比Vuex学习成本高
不可变值,纯函数
面试常考

Redux使用

基本概念

store state
action
reducer

//https://www.redux.org.cn/
import { createStore } from 'redux';

/**
 * 这是一个 reducer,形式为 (state, action) => state 的纯函数。
 * 描述了 action 如何把 state 转变成下一个 state。
 *
 * state 的形式取决于你,可以是基本类型、数组、对象、
 * 甚至是 Immutable.js 生成的数据结构。惟一的要点是
 * 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
 *
 * 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
 * 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
 */
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1;
  case 'DECREMENT':
    return state - 1;
  default:
    return state;
  }
}

// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);

// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
  console.log(store.getState())
);

// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1

单项数据流

dispatch(action)
reducer -> newState
subscribe触发通知
dispatch一个action会触发reducer,reducer更新状态,注意不可变值,再触发订阅通知,渲染到页面 image.png

react-redux

Provider
connect
mapStateToProps mapDispatchToProps

//index.js
import React from 'react'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp)

export default function () {
    return <Provider store={store}>
        <App />
    </Provider>
}
//AddTodo.js 
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

// 函数组件,接收 props 参数
let AddTodo = ({ dispatch }) => {
  // dispatch 即 props.dispatch

  let input

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault()
          if (!input.value.trim()) {
            return
          }
          // 创建一个 todo
          dispatch(addTodo(input.value))
          input.value = ''
        }}
      >
        <input
          ref={node => {
            input = node
          }}
        />
        <button type="submit">
          Add Todo
        </button>
      </form>
    </div>
  )
}

// connect 高阶组件 ,将 dispatch 作为 props 注入到 AddTodo 组件中
AddTodo = connect()(AddTodo)

export default AddTodo
//VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

// 不同类型的 todo 列表
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = state => {
  // state 即 vuex 的总状态,在 reducer/index.js 中定义
  return {
    // 根据完成状态,筛选数据
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = dispatch => {
  return {
    // 切换完成状态
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}

// connect 高阶组件,将 state 和 dispatch 注入到组件 props 中
const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

异步action

image.png

中间件有:
redux-thunk
redux-promise
redux-saga

中间件

image.png

image.png

image.png

Redux知识总结

基本概念
单项数据流
react-redux
异步action
中间件

React-router

面试考点并不多(前提是熟悉React)
路由模式(hash,H5 history),同vue-router
路由配置(动态路由、懒加载),同vue-router

路由模式

hash模式(默认),如abc.com/#/user/10\ H5 history模式,如abc.com/user/20\ 后者需要server端支持,因此无特殊需求可选择前者

toC客户端一般H5 history,toB控制后台一般hash

image.png

image.png

image.png

image.png

React-router总结

路由模式(hash、H5 history)
路由配置(动态路由、懒加载)
掌握基本使用

八、React原理

函数式编程

一种编程范式,概念比较多
纯函数
不可变值

回顾vdom和diff

h函数
vnode数据结构
patch函数
只比较同一层级,不跨级比较
tag不相同,则直接删掉重建,不再深度比较
tag和key,两者都相同,则认为是相同节点,不再深度比较
Vue2.x Vue3.0 React三者实现vdom细节都不同
核心概念和实现思路,都一样
面试主要考察后者,不用全部掌握细节

JSX本质

JSX等同于Vue模板
Vue模板不是html
JSX也不是JS

// 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

React.createElement即h函数,返回vnode
第一个参数,可能是组件,也可能是html tag
组件名,首字母必须大写(React规定)

image.png

合成事件

所有事件挂载到document上
event不是原生的,是SyntheticEvent合成事件对象
和Vue事件不同,和DOM事件也不同

// 1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力

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

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

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

image.png

为何要合成事件机制

更好的兼容性和跨平台
载到document,减少内存消耗,避免频繁解绑
方便事件的统一管理(如事务机制)

setState batchUpdate

有时异步(普通使用),有时同步(setTimeout、DOM事件)
有时合并(对象形式),有时不合并(函数形式)
后者比较好理解(像Object.assign),主要讲解前者

核心要点

setState主流程

image.png

image.png

batchUpdate机制

image.png

image.png

setState是同步还是异步

setState无所谓异步还是同步
看是否能命中batchUpdate机制
判断isBatchingUpdates

哪些能命中batchUpdate机制

生命周期(和它调用的函数)
React中注册的时间(和它调用的函数)
React可以“管理”入口

哪些不能命中batchUpdate机制

setTimeout setInterval等(和它调用的函数)
自定义的DOM时间(和它调用的函数)
React“管不到”的入口

transaction(事务)机制

image.png

image.png

image.png

组件渲染过程

JSX如何渲染为页面
setState之后如何更新页面
面试考察全流程

回顾Vue组件渲染和更新过程

参考:juejin.cn/post/712800…

image.png

回顾JSX本质和vdom

JSX即createElement函数
执行生成vnode
patch(elem,vnode)和patch(vnode,newVnode)

回顾dirtyComponents

image.png

讲解内容

组件渲染过程

props state
render()生成vnode
patch(elem,vnode)

组件更新过程

setState(newState) --> dirtyComponents(可能有子组件)
render()生成newVnode
patch(vnode,newVnode)

更新的两个阶段

上述的patch被拆分为两个阶段:
reconciliation阶段 - 执行diff算法,纯JS计算
commit阶段 - 将diff结果渲染DOM

可能会有性能问题

JS是单线程,且和DOM渲染共用一个线程
当组件足够复杂,组件更新时计算和渲染都压力大
同事再有DOM操作需求(动画,鼠标拖拽等),将卡顿

解决方案 fiber

将reconciliation阶段进行任务拆分(commit无法拆分)
DOM需要渲染时暂停,空闲时恢复
window.requestIdleCallback

关于fiber

React内部运行机制,开发者体会不到
了解背景和基本概念即可

小结

组件渲染和更新过程
更新的两个阶段,reconciliation及commit
React fiber

九、React面试真题演练

1.组件之间如何通讯

父子组件props
自定义事件
Redux和Context

2.JSX本质是什么

createElement
执行返回vnode

3.Context是什么?如何应用

父组件,向其下所有子孙组件传递信息
对一些简单的公共信息:主题色、语言等
复杂的公共信息,请用redux

4.shouldComponentUpdate用途

性能优化
配合“不可变值”一起使用,否则会出错

5.redux单项数据流

image.png

6.setState场景题--类似几率很大

import React from 'react'

class ListDemo extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
    }
    render() {
        return <p>{this.state.count}</p>
    }
    componentDidMount() {
        // count 初始值为 0
        this.setState({ count: this.state.count + 1 })
        console.log('1', this.state.count) // 0
        this.setState({ count: this.state.count + 1 })
        console.log('2', this.state.count) // 0
        setTimeout(() => {
            this.setState({ count: this.state.count + 1 })
            console.log('3', this.state.count) // 2
        },0)
        setTimeout(() => {
            this.setState({ count: this.state.count + 1 })
            console.log('4', this.state.count) // 3
        },0)
    }
}

export default ListDemo

7.什么是纯函数

返回一个新值,没有副作用(不会“偷偷”修改其他值)
重点:不可变值
如arr1 = arr.slice()

8.React组件生命周期--非常重要必考

单组件生命周期
父子组件生命周期
注意SCU

9.React发起ajax应该在哪个生命周期

同Vue
componentDidMount

10.渲染列表,为何使用key--非常重要必考

同Vue,必须用key,且不能是index和random
diff算法中通过tag和key来判断,是否是sameNode
减少渲染次数,提升渲染性能

11.函数组件和class组件区别

纯函数,输入props,输出JSX
没有实例,没有生命周期,没有state
不能扩展其他方法

12.什么是受控组件

表单的值,受state控制
需要自行监听oChange,更新state
对比非受控组件

13.何时使用异步组件

同Vue
加载大组件
路由懒加载

14.redux如何进行异步请求

使用异步action
如redux-thunk

15.react-router如何配置懒加载

image.png

16.PureComponent有何区别

实现了浅比较的shouldComponentUpdate
优化性能
但要结合不可变值使用

17.React事件和DOM事件的区别

所有事件都挂载到document上
event不是原生的,是SyntheticEvent合成事件对象
dispatchEvent

18.React性能优化

渲染列表加key
自定义事件、DOM事件及时销毁
合理使用异步组件
减少函数bind this的次数
合理使用SCU PureComponent和memo
合理使用Immutable.js
webpack层面的优化(后面会讲)
前端通用的性能优化,如图片懒加载
使用SSR

19.React和Vue的区别

都支持组件化
都是数据驱动视图
都使用vdom操作DOM
React使用JSX拥抱JS,Vue使用模板拥抱html
React函数式编程,Vue声明式编程
React更多需要自力更生,Vue把想要的都给你


VUE 与 React 区别

React 的思路是 HTML in JavaScript 也可以说是 All in JavaScript,通过 JavaScript 来生成 HTML,所以设计了 JSX 语法,还有通过 JS 来操作 CSS,社区的styled-component、JSS等。

Vue 是把 HTML,CSS,JavaScript 组合到一起,用各自的处理方式,Vue 有单文件组件,可以把 HTML、CSS、JS 写到一个文件中,HTML 提供了模板引擎来处理。

React 整体是函数式的思想,在 React 中是单向数据流,推崇结合 immutable 来实现数据不可变。

而 Vue 的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立 Watcher 来监听,当属性变化的时候,响应式的更新对应的虚拟 DOM。

如上,所以 React 的性能优化需要手动去做,而Vue的性能优化是自动的,但是Vue的响应式机制也有问题,就是当 state 特别多的时候,Watcher 会很多,会导致卡顿。

React 与 VUE 共同点

React 与 Vue 存在很多共同点,例如他们都是 JavaScript 的 UI 框架,专注于创造前端的富应用。不同于早期的 JavaScript 框架“功能齐全”,Reat 与 Vue 只有框架的骨架,其他的功能如路由、状态管理等是框架分离的组件。

优势

React

  • 灵活性和响应性:它提供最大的灵活性和响应能力。
  • 丰富的JavaScript库:来自世界各地的贡献者正在努力添加更多功能。
  • 可扩展性:由于其灵活的结构和可扩展性,React已被证明对大型应用程序更好。
  • 不断发展: React得到了Facebook专业开发人员的支持,他们不断寻找改进方法。
  • Web或移动平台: React提供React Native平台,可通过相同的React组件模型为iOS和Android开发本机呈现的应用程序。

Vue

  • 易于使用: Vue.js包含基于HTML的标准模板,可以更轻松地使用和修改现有应用程序。
  • 更顺畅的集成:无论是单页应用程序还是复杂的Web界面,Vue.js都可以更平滑地集成更小的部件,而不会对整个系统产生任何影响。
  • 更好的性能,更小的尺寸:它占用更少的空间,并且往往比其他框架提供更好的性能。
  • 精心编写的文档:通过详细的文档提供简单的学习曲线,无需额外的知识; HTML和JavaScript将完成工作。
  • 适应性:整体声音设计和架构使其成为一种流行的JavaScript框架。
  • 它提供无障碍的迁移,简单有效的结构和可重用的模板。

总结

如上所说的 Vue 的响应式机制也有问题,当 state 特别多的时候,Watcher 会很多,会导致卡顿,所以大型应用(状态特别多的)一般用 React,更加可控。

可对于易用性来说,VUE 是更容易上手的,对于项目来说新人更容易接手。

技术没有哪个更好或者是更优秀,只要适合自己的才是最合适的。