脚手架

基本语法

  • jsx 语法
    • 基本语法
import React from 'react'
import './style.css'
import List from '../List'

class JSXBaseDemo extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'xmllein',
      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
import React from 'react'

class List extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'React',
      list: ['a', 'b', 'c'],
    }
  }
  render() {
    return (
      <div>
        <p onClick={this.changeName.bind(this)}>{this.state.name}</p>
        <ul>
          {this.state.list.map((item, index) => {
            return <li key={index}>{item}</li>
          })}
        </ul>
        <button onClick={this.addItem.bind(this)}>添加一项</button>
      </div>
    )
  }
  changeName() {
    this.setState({
      name: 'xmllein',
    })
  }
  addItem() {
    this.setState({
      list: this.state.list.concat(`${Date.now()}`), // 使用不可变值
    })
  }
}

export default List
  • 条件
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
  • 事件
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 指向
    this.clickHandler1 = this.clickHandler1.bind(this)
  }
  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 是原生事件对象 react 17 绑到root上
    // 3. 所有的事件,都被挂载到 document 上
    // 4. 和 DOM 事件不一样,和 Vue 事件也不一样
  }
  // 传递参数
  clickHandler4(id, title, event) {
    console.log(id, title)
    console.log('event', event) // 最后追加一个参数,即可接收 event
  }
}

export default EventDemo
  • 表单
import React from 'react'

class FormDemo extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'xmllein',
      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
/**
 * @description 演示 props 和事件
 * @author xmllein老师
 */

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
  • state 和 setState
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 异步更新的话,更新前会被合并 ----------------------------

    // // 传入对象,会被合并(类似 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 进行属性设置,这样违反不可变值

高级使用

  • 函数组件

    • 函数组件和类组件的区别
      • 纯函数
      • 函数组件没有实例,没有生命周期,没有 state
      • 函数组件没有 this,没有 refs
// 函数组件
function List(props) {
  const { list } = this.props
  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}
  • 受控与非受控组件

    • 受控组件:表单元素的值受 state 控制
    • 非受控组件:表单元素的值不受 state 控制
import React from 'react'

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'xmllein',
      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
  • refs

  • Portals

    • 什么是 Portals?:Portals 可以让你把子节点渲染到父组件 DOM 层次以外的 DOM 节点上
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
  • context

    • 什么是 context?:context 可以让你不通过 props 层层传递数据,就能在组件树中传递数据
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
  • 异步组件

    • React 异步组件的实现原理是什么?:React.lazy 和 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
  • 性能优化

    • React.memo: React.memo 是一个高阶组件,它与 React.PureComponent 非常相似,但它适用于函数组件,但不适用于 class 组件。
  • scu

    • shouldComponentUpdate: shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。
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 ,故意写的错误用法
    // this.state.list.push({
    //     id: `id-${Date.now()}`,
    //     title
    // })
    // this.setState({
    //     list: this.state.list
    // })
  }
}

export default TodoListDemo
  • 纯组件

    • PureComponent: PureComponent 与 Component 非常相似。它们的区别在于 PureComponent 通过 prop 和 state 的浅比较来实现 shouldComponentUpdate()。
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
  • 不可变值(immutableopen in new window)

  • 高阶组件

    • 高阶组件是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
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) // 返回高阶函数
  • render props

    • render props 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术。
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

Hooks

  • useState

    • useState 是一个函数,它的作用是用来声明状态变量。
import React, { useState } from 'react'

// 子组件
function Child({ userInfo }) {
  // render: 初始化 state
  // re-render: 只恢复初始化的 state 值,不会再重新设置新的值
  //            只能用 setName 修改
  const [name, setName] = useState(userInfo.name)

  return (
    <div>
      <p>Child, props name: {userInfo.name}</p>
      <p>Child, state name: {name}</p>
    </div>
  )
}

function App() {
  const [name, setName] = useState('xmllein')
  const userInfo = { name }

  return (
    <div>
      <div>
        Parent &nbsp;
        <button onClick={() => setName('测试React')}>setName</button>
      </div>
      <Child userInfo={userInfo} />
    </div>
  )
}

export default App
  • useEffect

    • useEffect 是一个函数,它的作用是用来声明副作用操作。其作用:模拟类组件的生命周期函数。
import React, { useState, useEffect } from 'react'

function LifeCycles() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('xmllein')

  // // 模拟 class 组件的 DidMount 和 DidUpdate
  // useEffect(() => {
  //     console.log('在此发送一个 ajax 请求')
  // })

  // // 模拟 class 组件的 DidMount
  // useEffect(() => {
  //     console.log('加载完了')
  // }, []) // 第二个参数是 [] (不依赖于任何 state)

  // // 模拟 class 组件的 DidUpdate
  // useEffect(() => {
  //     console.log('更新了')
  // }, [count, name]) // 第二个参数就是依赖的 state

  // 模拟 class 组件的 DidMount
  useEffect(() => {
    let timerId = window.setInterval(() => {
      console.log(Date.now())
    }, 1000)

    // 返回一个函数
    // 模拟 WillUnMount
    return () => {
      window.clearInterval(timerId)
    }
  }, [])

  function clickHandler() {
    setCount(count + 1)
    setName(name + '2020')
  }

  return (
    <div>
      <p>
        你点击了 {count}{name}
      </p>
      <button onClick={clickHandler}>点击</button>
    </div>
  )
}

export default LifeCycles
  • useContext

    • useContext 是一个函数,它的作用是用来在函数组件中使用 context。其作用: 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定。
import React, { useContext } from 'react'

// 主题颜色
const themes = {
  light: {
    foreground: '#000',
    background: '#eee',
  },
  dark: {
    foreground: '#fff',
    background: '#222',
  },
}

// 创建 Context
const ThemeContext = React.createContext(themes.light) // 初始值

function ThemeButton() {
  const theme = useContext(ThemeContext)

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      hello world
    </button>
  )
}

function Toolbar() {
  return (
    <div>
      <ThemeButton></ThemeButton>
    </div>
  )
}

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar></Toolbar>
    </ThemeContext.Provider>
  )
}

export default App
  • useReducer

    • useReducer 是一个函数,它的作用是用来声明 reducer。其作用: 模拟类组件的 setState
import React, { useReducer } from 'react'

const initialState = { count: 0 }

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    default:
      return state
  }
}

function App() {
  // 很像 const [count, setCount] = useState(0)
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <div>
      count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>decrement</button>
    </div>
  )
}

export default App
  • useCallback

  • useCallback 是一个函数,它的作用是用来缓存函数。其作用:缓存函数,避免不必要的渲染。

import React, { useState, memo, useMemo, useCallback } from 'react'

// 子组件,memo 相当于 PureComponent
const Child = memo(({ userInfo, onChange }) => {
  console.log('Child render...', userInfo)

  return (
    <div>
      <p>
        This is Child {userInfo.name} {userInfo.age}
      </p>
      <input onChange={onChange}></input>
    </div>
  )
})

// 父组件
function App() {
  console.log('Parent render...')

  const [count, setCount] = useState(0)
  const [name, setName] = useState('xmllein')

  // 用 useMemo 缓存数据
  const userInfo = useMemo(() => {
    return { name, age: 21 }
  }, [name])

  // function onChange(e) {
  //     console.log(e.target.value)
  // }
  // 用 useCallback 缓存函数
  const onChange = useCallback((e) => {
    console.log(e.target.value)
  }, [])

  return (
    <div>
      <p>
        count is {count}
        <button onClick={() => setCount(count + 1)}>click</button>
      </p>
      <Child userInfo={userInfo} onChange={onChange}></Child>
    </div>
  )
}

export default App
  • useMemo

    • useMemo 是一个函数,它的作用是用来缓存数据。其作用:缓存数据,避免不必要的渲染。
import React, { useState, memo, useMemo } from 'react'

// 子组件
// function Child({ userInfo }) {
//     console.log('Child render...', userInfo)

//     return <div>
//         <p>This is Child {userInfo.name} {userInfo.age}</p>
//     </div>
// }
// 类似 class PureComponent ,对 props 进行浅层比较
const Child = memo(({ userInfo }) => {
  console.log('Child render...', userInfo)

  return (
    <div>
      <p>
        This is Child {userInfo.name} {userInfo.age}
      </p>
    </div>
  )
})

// 父组件
function App() {
  console.log('Parent render...')

  const [count, setCount] = useState(0)
  const [name, setName] = useState('xmllein')

  // const userInfo = { name, age: 20 }
  // 用 useMemo 缓存数据,有依赖
  const userInfo = useMemo(() => {
    return { name, age: 21 }
  }, [name])

  return (
    <div>
      <p>
        count is {count}
        <button onClick={() => setCount(count + 1)}>click</button>
      </p>
      <Child userInfo={userInfo}></Child>
    </div>
  )
}

export default App
  • useRef

    • useRef 是一个函数,获取 DOM 节点或者保存变量。其作用:获取 DOM 节点或者保存变量。
import React, { useRef, useEffect } from 'react'

function UseRef() {
  const btnRef = useRef(null) // 初始值

  // const numRef = useRef(0)
  // numRef.current

  useEffect(() => {
    console.log(btnRef.current) // DOM 节点
  }, [])

  return (
    <div>
      <button ref={btnRef}>click</button>
    </div>
  )
}

export default UseRef
  • useImperativeHandle

    • useImperativeHandle 是一个函数,它的作用是用来自定义暴露给父组件的实例值。其作用:自定义暴露给父组件的实例值。
import React, { useRef, useImperativeHandle, forwardRef } from 'react'

// 子组件
const Child = forwardRef((props, ref) => {
  const inputRef = useRef(null)

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus()
    },
  }))

  return (
    <div>
      <input ref={inputRef}></input>
    </div>
  )
})

// 父组件
function UseImperativeHandle() {
  const childRef = useRef(null)

  return (
    <div>
      <button onClick={() => childRef.current.focus()}>focus</button>
      <Child ref={childRef}></Child>
    </div>
  )
}

export default UseImperativeHandle
  • useLayoutEffect

    • useLayoutEffect 是一个函数,它的作用是在 DOM 更新完成后执行。其作用:在 DOM 更新完成后执行。
import React, { useState, useEffect, useLayoutEffect } from 'react'

function UseLayoutEffect() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    if (count === 0) {
      const now = Date.now()
      while (Date.now() - now < 3000) {}
    }
  }, [count])

  useLayoutEffect(() => {
    if (count === 0) {
      const now = Date.now()
      while (Date.now() - now < 3000) {}
    }
  }, [count])

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>click</button>
    </div>
  )
}

export default UseLayoutEffect
  • 自定义 Hooks
import { useState, useEffect } from 'react'

function useMousePosition() {
  const [x, setX] = useState(0)
  const [y, setY] = useState(0)

  useEffect(() => {
    function mouseMoveHandler(event) {
      setX(event.clientX)
      setY(event.clientY)
    }

    // 绑定事件
    document.body.addEventListener('mousemove', mouseMoveHandler)

    // 解绑事件
    return () =>
      document.body.removeEventListener('mousemove', mouseMoveHandler)
  }, [])

  return [x, y]
}

export default useMousePosition
import { useState, useEffect } from 'react'
import axios from 'axios'

// 封装 axios 发送网络请求的自定义 Hook
function useAxios(url) {
  const [loading, setLoading] = useState(false)
  const [data, setData] = useState()
  const [error, setError] = useState()

  useEffect(() => {
    // 利用 axios 发送网络请求
    setLoading(true)
    axios
      .get(url) // 发送一个 get 请求
      .then((res) => setData(res))
      .catch((err) => setError(err))
      .finally(() => setLoading(false))
  }, [url])

  return [loading, data, error]
}

export default useAxios

// 第三方 Hook
// https://nikgraf.github.io/react-hooks/
// https://github.com/umijs/hooks
  • 为何使用 Hooks,要依赖调用顺序?

    • 为了保证每次渲染都有相同的顺序,这样才能保证每次渲染都是相同的结果。
  • class 组件中有什么问题?

    • 代码冗余
    • 逻辑分散
    • this 指向问题
    • 复用问题
  • 逻辑复用

    • HOC:高阶组件
    • render props:渲染属性
    • Hooks:钩子函数

周边工具

  • redux-toolkitopen in new window

    • redux-toolkit 是什么?: redux-toolkit 是一个 redux 工具包,它可以让你更加方便的使用 redux
    • configureStore: configureStore({ reducer, middleware, devTools, preloadedState, enhancers })
    • createSlice: createSlice({ name, initialState, reducers, extraReducers, sliceCaseReducers })
    • createAsyncThunk: createAsyncThunk(typePrefix, payloadCreator, options)
    • createEntityAdapter: createEntityAdapter({ selectId, sortComparer, ...rest })
    • createReducer: createReducer(initialState, builderCallback)
    • createNextState: createNextState(baseState, draft => {})
    • createSerializableStateInvariantMiddleware: createSerializableStateInvariantMiddleware(options)
    • createImmutableStateInvariantMiddleware: createImmutableStateInvariantMiddleware(options)
    • createMiddleware: createMiddleware(options)
    • createAction: createAction(type, prepareAction)
  • reduxopen in new window

    • store: createStore(reducer, [preloadedState], [enhancer])
    • action: dispatch(action)
    • reducer: (state, action) => newState
    • middleware: ({ getState, dispatch }) => next => action => {}
    • dispatch: dispatch(action)
    • subscribe: subscribe(listener): listener 会在 dispatch 时被调用
  • react-reduxopen in new window

  • provider: <Provider store>

  • connect: connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

  • useSelector: useSelector(selector, equalityFn)

  • useDispatch: useDispatch()

  • useStore: useStore()

  • redux-thunkopen in new window

    • thunk 是什么?: ({ getState, dispatch }) => next => action => {}: action 可以是一个函数,函数接收 dispatchgetState 作为参数。
  • dvaopen in new window

  • dva 是什么?: dva 是基于 reduxredux-saga 的数据流方案。

  • redux-sagaopen in new window

  • mobxopen in new window

    • mobx 是什么?: mobx 是一个状态管理库,它可以让你的应用状态变得可预测。
    • observable: observable(value, options?, name?)
    • computed: computed(() => value, options?, name?)
    • autorun: autorun(() => {})
    • reaction: reaction(() => value, (value, reaction) => {})
    • action: action(() => {})
    • runInAction: runInAction(() => {})
    • when: when(() => value, () => {})
    • whyRun: whyRun(() => {})
    • spy: spy(() => {})
    • toJS: toJS(value, options?)
    • toJSON: toJSON(value, options?)
    • isObservable: isObservable(value)
    • isObservableObject: isObservableObject(value)
  • immeropen in new windowimmer 是一个不可变值库,它可以让你以 mutate 的方式来修改不可变值。

  • react-routeropen in new windowreact-router 是一个路由库,它可以让你的应用拥有路由功能。

  • redux-persistopen in new window: redux-persist 是一个持久化存储库,它可以让你的应用状态持久化存储。

  • ahooksopen in new window: ahooks 是一个 React Hooks 库,它可以让你的应用更加简单。

Typescript 整合

  • React 与 TS 配合之基础 props 限制
  • React 与 TS 配合之 children 与 event 限制
  • React 与 TS 配合之 style 与 component 限制
  • React 与 TS 配合之 use 函数限制
import React from 'react'

interface HeaderProps {
  username: string
}

interface WelcomeProps {
  name?: string
  count?: number
  list: string[]
  info: { username: string; age: number }
  status?: 'loading' | 'success' | 'error'
  // 子元素
  children?: React.ReactNode
  // 事件
  handleClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
  // 样式
  style?: React.CSSProperties
  // 组件
  component: React.ComponentType<HeaderProps>
}

// const Welcome = (props: WelcomeProps) => {
//   return (
//     <h1>
//       Hello, {props.name}, {props.count}
//     </h1>
//   )
// }

function Header(props: HeaderProps) {
  return <div>hello Header</div>
}

// 等同于
const Welcome: React.FC<WelcomeProps> = (props) => {
  // 解构赋值 默认值
  const { name = 'world', count } = props
  // 类型断言
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value)
  }
  // useState
  const [msg, setMsg] = React.useState<number | string>('hello')

  // useRef
  const myRef = React.useRef<HTMLInputElement>(null)

  // useEffect
  React.useEffect(() => {
    console.log(myRef.current?.value) // 可选链(类型保护)
    console.log(myRef.current!.value!.length) // 非空断言, 一定有值(慎用)
  }, [])

  const handleClick = () => {
    setMsg('world')
    console.log(myRef.current?.value)
  }

  return (
    <>
      <h1>
        Hello, {name}, {count}
      </h1>
      <ul>
        {props.list.map((item, index) => {
          return <li key={index}>{item}</li>
        })}
      </ul>
      <p>{props.info.age}</p>
      <div>{props.children}</div>
      <button onClick={props.handleClick}>点击事件</button>
      <input type='text' value={count} onChange={handleChange} ref={myRef} />
      <p>msg: {msg}</p>
      <button onClick={handleClick}>修改msg</button>
      <div>
        <props.component username='hello'></props.component>
      </div>
    </>
  )
}
// 默认值
// Welcome.defaultProps = {
//   name: 'hello',
// }

export default function Demo01() {
  return (
    <div>
      <h2>Demo01</h2>
      <Welcome
        count={123}
        list={['a', 'b', 'c']}
        info={{ username: 'hello', age: 1200 }}
        status='loading'
        style={{ color: 'red' }}
        component={Header}
        handleClick={(ev) => {
          console.log(ev)
        }}
      >
        hello world
      </Welcome>
    </div>
  )
}
  • React 与 TS 配合之类组件类型限制
import React, { Component } from 'react'

interface WelcomeProps {
  msg: string
  count: number
}

interface WelcomeState {
  username: string
}

class Welcome extends Component<WelcomeProps, WelcomeState> {
  state = {
    username: 'xiaoming',
  }
  render() {
    return <div>hello Welcome {this.state.username}</div>
  }
}

export default function App() {
  return (
    <div>
      <h2>05_react-ts</h2>
      <Welcome msg='hello' count={123} />
    </div>
  )
}
  • ReactRouter 路由如何使用 TS 进行开发
import React from 'react'
import { createBrowserRouter } from 'react-router-dom'
import type { RouteObject } from 'react-router-dom'

import App from '../App'
import Home from '../views/Home'
import About from '../views/About'
import Login from '../views/Login'

// 声明路由表的类型
declare module 'react-router' {
  interface NonIndexRouteObject {
    meta?: {
      title: string
    }
  }
  interface IndexRouteObject {
    meta?: {
      title: string
    }
  }
}

// 路由表
export const routes: RouteObject[] = [
  {
    path: '/',
    element: React.createElement(App),
    meta: { title: '首页' },
    children: [
      {
        path: 'home',
        element: React.createElement(Home),
        meta: { title: 'home' },
      },
      {
        path: 'about',
        element: React.createElement(About),
        meta: { title: 'About' },
      },
      {
        path: 'login',
        element: React.createElement(Login),
        meta: { title: 'Login' },
      },
    ],
  },
]

const router = createBrowserRouter(routes)

export default router
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import reportWebVitals from './reportWebVitals'
import { RouterProvider } from 'react-router-dom'
import router from './router'
import { Provider } from 'react-redux'
import store from './store'

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <RouterProvider router={router}></RouterProvider>
    </Provider>
  </React.StrictMode>
)

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
  • ReduxToolkit 状态管理如何使用 TS 进行开发
import { configureStore } from '@reduxjs/toolkit'
import { useDispatch } from 'react-redux'

import counterReducer from './modules/counter'
import messageReducer from './modules/message'

const store = configureStore({
  reducer: {
    counter: counterReducer,
    message: messageReducer,
  },
})

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>()

export default store
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

type CounterState = { count: number; doubleCount: number }
// type Counter = { count: number }

const initialState: CounterState = { count: 0, doubleCount: 0 }

export const counterTestActions = createAsyncThunk(
  'counter/testAction',
  async () => {
    const ret = await new Promise((resolve) => {
      setTimeout(() => {
        resolve({ count: 10 })
      }, 2000)
    })
    return ret
  }
)

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    ...initialState,
    doubleCount: initialState.count * 2,
  } as CounterState,
  reducers: {
    increment(state, action: PayloadAction<number>) {
      state.count += action.payload
      state.doubleCount = state.count * 2
    },
    decrement(state) {
      state.count -= 1
    },
  },
  extraReducers: (builders) => {
    // 这里的 action 是 createAsyncThunk 返回的 action
    builders
      .addCase(counterTestActions.fulfilled, (state, action) => {
        // state.count = (action.payload as Counter).count
        const { count } = action.payload as { [index: string]: unknown }
        state.count = count as number
        state.doubleCount = state.count * 2
      })
      .addCase(counterTestActions.pending, (state) => {
        console.log('pending')
      })
      .addCase(counterTestActions.rejected, (state) => {
        console.log('rejected')
      })
  },
})

export default counterSlice.reducer
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
// 异步
export const messageTestAction = createAsyncThunk(
  'message/testAction',
  async () => {
    const ret = await new Promise((resolve) => {
      setTimeout(() => {
        resolve({ msg: 'hello async' })
      }, 2000)
    })
    return ret
  }
)

const messageSlice = createSlice({
  name: 'message',
  initialState: { msg: 'hello' },
  reducers: {
    change(state, action: PayloadAction<string>) {
      state.msg = action.payload
    },
    reset(state) {
      state.msg = 'hello'
    },
  },
})

export default messageSlice.reducer
import React from 'react'
import { useSelector } from 'react-redux'
import type { RootState } from '../store'
import { useAppDispatch } from '../store'
import { counterTestActions } from '../store/modules/counter'
export default function Login() {
  const count = useSelector((state: RootState) => state.counter.count)
  const doubleCount = useSelector(
    (state: RootState) => state.counter.doubleCount
  )

  const dispatch = useAppDispatch()

  const handleClick = () => {
    // dispatch({ type: 'counter/increment', payload: 1 })
    dispatch(counterTestActions())
  }
  return (
    <div>
      <h1>Login</h1>
      <p>count: {count}</p>
      <p>doubleCount: {doubleCount}</p>
      <button onClick={handleClick}>修改count</button>
    </div>
  )
}

面试题目

  • 打印出 state 值
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
  • React fiber 是什么? fiber 是一个数据结构,它可以让 React 更加高效的执行渲染任务。

  • React fiber 为什么要使用链表?: fiber 使用链表的原因是因为链表的插入和删除操作比较快。

    • 将 reconciliation 阶段进行任务拆分(commit 无法拆分)
    • dom 需要渲染的时候,可以暂停任务,让出主线程,让浏览器渲染
    • window.requestIdleCallback
  • vdom 与 diff 算法

    • vdom 是什么?: vdom 是一个虚拟 dom,它可以让我们在 js 中描述 dom 结构。

    • h 函数: h(type, props, ...children)

    • vnode 数据结构: { type, props, children }

    • patch 函数: patch(container, vnode)

    • diff 算法: diff 算法是一种算法,它可以让我们找出两个 vnode 之间的差异。

    • diff 算法策略: diff 算法的策略是 O(n),它会遍历新旧两个 vnode,然后找出差异。

      • 同级比较: diff 算法会先比较同级节点,如果同级节点不同,那么就会直接替换。
      • tag 不相同: diff 算法会直接替换。
      • tag 和 key 两者相同, 但是 props 不同: diff 算法会更新 props。
      • tag 和 key 两者相同, 但是 children 不同: diff 算法会更新 children。
      • tag 和 key 两者相同, 但是 props 和 children 都不同: diff 算法会更新 props 和 children。
      • tag 和 key 两者相同, 但是 props 和 children 都相同: diff 算法不会更新。
  • React 事件和 dom 事件区别:

    • React 事件是合成事件,它是通过事件委托的方式来实现的。
    • dom 事件是原生事件,它是通过在 dom 上直接绑定事件来实现的。
    • React 事件和 dom 事件的区别是: React 事件是通过事件委托的方式来实现的,而 dom 事件是通过在 dom 上直接绑定事件来实现的。
  • React 性能优化:

    • 使用 shouldComponentUpdate 或者 React.memo 来避免不必要的渲染。
    • 使用 useCallback 来避免不必要的渲染。
    • 使用 useMemo 来避免不必要的渲染。
    • 使用 React.lazySuspense 来实现代码分割。
    • 渲染列表的时候,使用 key 来提高渲染性能。
    • 自定义事件,dom 事件,要及时销毁
    • 减少 bind this 操作,使用箭头函数
    • 合理使用 immutable.js, immer.js 等不可变值库
  • React 和 Vue 区别?

    • 相同:

      • 都是组件化开发
      • 都是声明式开发
      • 都是数据驱动视图
      • 都是使用虚拟 dom
      • 都是函数式编程
    • 不同:

      • React 是通过 JSX 来实现模板的,而 Vue 是通过 template 来实现模板的。
      • React 是通过 props 来实现组件之间的通信的,而 Vue 是通过 props 和 emit 来实现组件之间的通信的。
      • React 是通过 context 来实现跨层级通信的,而 Vue 是通过 provide 和 inject 来实现跨层级通信的。
      • React 是通过高阶组件来实现代码复用的,而 Vue 是通过 mixins 来实现代码复用的。
      • React 是通过函数组件和类组件来实现组件的,而 Vue 是通过对象来实现组件的。
      • React 是通过状态提升来实现跨组件通信的,而 Vue 是通过事件总线来实现跨组件通信的。
      • React 是通过 shouldComponentUpdate 或者 React.memo 来实现组件性能优化的,而 Vue 是通过 v-if 和 keep-alive 来实现组件性能优化的。
      • React 是通过 React.lazy 和 Suspense 来实现代码分割的,而 Vue 是通过异步组件来实现代码分割的。

思维笔记

参考资料