React高级特性
@ 姜波 | 星期五,七月 10 日,2020 年 | 4 分钟阅读 | 更新于 星期五,七月 10 日,2020 年

函数组件

  • 纯函数,输入props,输出JSX
  • 没有实例,没有生命周期,没有state
  • 不能扩展其他方法
// class组件
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>
  }
}
// 函数组件
function List(props) {
  const {list} = props
  
  return <ul>{list.map((item,index) => {
    return <li key={item.id}>
       <span>{item.title}</span>
      </li>
  })}</ul>
}

非受控组件

  • ref
  • defaultValue、defaultChecked
  • 手动操作DOM元素
import React from 'react'

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: 'wilberjiang',
            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时,再使用非受控组件

Portals

  • 组件默认会按照既定层级嵌套渲染
  • 如何让组件渲染到父组件以外
import React from 'react'
import ReactDOM from 'react-dom'

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

        // 使用 Portals 渲染到 body 上。
        return ReactDOM.createPortal(
            <div className="modal">{this.props.children}</div>,
            document.body // DOM 节点
        )
    }
}

export default App

使用场景

  • Overflow:hidden
  • 父组件z-index值太小
  • fixed需要放在body第一层级

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

异步组件

  • 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

性能优化

SCU基本用法

shouldComponentUpdate(nextProps,nextState){
	if(nextState.count !== this.state.count){
    return true // 可以渲染
  }
  return false // 不重复渲染
}

SCU使用总结

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

PureComponent和memo

  • PureComponent,SCU中实现了浅比较
  • memo,函数组件中的PureComponent
  • 浅比较已使用大部分情况(尽量不要做深度比较)
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() {/*浅比较*/}
}
function MyComponent(props){
  /* 使用props渲染 */
}
function areEqual(prevProps,nextProps){
  /*
  如果把nextProps传入render方法的返回结果与
  将prevProps传入render方法的返回结果一致则返回true,
  否则返回false
  */
}
export default React.memo(MyComponent,areEqual)

immutable.js

  • 彻底拥抱“不可变值”
  • 基于共享数据(不是深拷贝),速度好
  • 有一定学习和迁移成本,按需使用
const map1 = Immutable.Map({a:1, b:2, c:3})
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50

组件公共逻辑的抽离

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

高阶组件HOC

// 高阶组件不是一种功能,而是一种模式
const HOCFactory = (Component) => {
  class HOC extends React.Component {
    // 在此定义多个组件的公共逻辑
    render(){
      // 返回拼装的结果
      return <Component {...this.props} />
    }
  }
  return HOC
}
const EnhancedComponent1 = HOCFactory(WrappedComponent1)
const EnhancedComponent2 = HOCFactory(WrappedComponent2)
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的核心思想
// 通过一个函数将class组件的state作为props传递给纯函数组件
class Factory extends React.Component {
  constructor(){
    this.state = {
      /* state即多个组件的公共逻辑的数据 */
    }
  }
  /* 修改state */
  render(){
    return <div>{this.props.render(this.state)}</div>
  }
}

const App = () => {
  <Factory render={
    /* render 是一个函数组件 */
    (props) => <p>{props.a} {props.b} ...</p>
  }/>
}
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

公众号

Image text

QQ

Image text

微信

Image text

微信打赏

Image text

社交链接