react小分享1

117 阅读10分钟

1. JSX

  • JSX是一种JS和HTML混合的语法, 将组件的结构、数据甚至样式都聚合在一起定义组件
  • JSX其实只是一种语法糖,最终会通过babeljs转译成createElement语法,以下代码等价
  • 元素是构成 React 应用的最小单位
  • 元素用来描述你在屏幕上看到的内容
  • React当中的元素事实上是普通的JS对象, ReactDOM来确保浏览器中的DOM数据和React元素保持一致
<h1 className="title" style={{color:'red'}}>hello</h1>
React.createElement("h1", {
  className: "title",
  style: {
    color: 'red'
  }
}, "hello");

createElement的结果

{
  type:'h1',
  props:{
    className: "title",
    style: {
      color: 'red'
    }
  },
  children:"hello"
}

2. 函数(定义的)组件

  • 函数组件接收一个单一的props对象并返回了一个React元素
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

3. 类(定义的)组件

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

5. 虚拟DOM

5.1 index.js

import React from './react';
import ReactDOM from './react-dom';
//let element = <h1 className="title" style={{color:'red',fontSize:'24px'}}></h1>
//let element = React.createElement('h1',{className:'title',style:{color:'red',fontSize:'50px'}},'hello');
//console.log(JSON.stringify(element));
//function Welcome(props){
//    return React.createElement('h1',{className:'title'},props.title);
//}
class Welcome extends React.Component{
    render(){
        return React.createElement('h1',{className:'title'},this.props.title);
    }
}
let element = React.createElement(Welcome,{title:'标题'});
ReactDOM.render(element, document.getElementById('root'));

5.2 react.js

import createElement from './element';
class Component{
    static isReactComponent = true
    constructor(props){
      this.props = props;
    }
  }
export default {
    createElement,Component
}

5.3 element.js

const ReactElement = function(type,props) {
    const element = {
        type: type,
        props: props,
    };
    return element;
}

function createElement(type,config,children){
  let propName;
  const props = {};
  for (propName in config) {
    props[propName] = config[propName];
  }
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    props.children = Array.prototype.slice.call(arguments,2);
  }
  return ReactElement(type,props);
}
export default createElement;

5.4 react-dom.js

function render(element,container){
    if(typeof element == 'string'){
      return container.appendChild(document.createTextNode(element))
    }
    let type,props;
    type = element.type;  
    props = element.props; 
    if(type.isReactComponent){//如果为true说明它是一个类组件
      element = new type(props).render();
      type = element.type;  
      props = element.props;
    }else  if(typeof type =='function'){
      element = type(props);
      type = element.type;  
      props = element.props;
    }
    let domElement = document.createElement(type);
    for(let propName in props){
        if(propName === 'children'){
          let children = props[propName];
          children = Array.isArray(children)?children:[children];
          children.forEach(child=>render(child,domElement));
        }else if(propName === 'className'){
          domElement.className = props[propName];
        }else if(propName === 'style'){
          let styleObj = props[propName];
          /**
          for(let attr in styleObj){
              domElement.style[attr] =  styleObj[attr];
          }
           */
          let cssText = Object.keys(styleObj).map(attr=>{
            return `${attr.replace(/([A-Z])/g,function(){ return"-"+arguments[1]})}:${styleObj[attr]}`;
          }).join(';');
          domElement.style.cssText = cssText;
        }else{
          domElement.setAttribute(propName,props[propName]);
        }
    }
    container.appendChild(domElement);
  }
export default {render};

6. State

  • 构造函数是唯一可以给 this.state 赋值的地方
  • 出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用
  • 因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态
  • 可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数

image.png

6.1 setState 异步还是同步?

  • 有时异步:普通使用
  • 有时同步: setTimeout、DOM事件

先给出答案: 有时表现出异步,有时表现出同步

  • setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。
  • setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

解释:

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

哪些能命中batchUpdate机制

  • 生命周期(和它调用的函数)
  • React中注册的事件(和它调用的函数)
  • React可以管理的入口

哪些不能命中batchUpdate机制

  • setTimeout setInterval等(和它调用的函数)
  • 自定义的DOM事件(和它调用的函数)
  • React管不到的入口

setState什么时候合并,什么时候不合并 有时合并:对象形式 有时不合并:函数形式

6.2 事务实现setState

  • 源码
  • 一个所谓的 Transaction 就是将需要执行的 method 使用 wrapper 封装起来,再通过 Transaction 提供的 perform 方法执行
  • 而在 perform 之前,先执行所有 wrapper 中的 initialize 方法;perform 完成之后(即 method 执行后)再执行所有的 close 方法
  • 一组 initialize 及 close 方法称为一个 wrapper
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * 
 

6.3 transaction

function setState() {
    console.log('setState')
}
class Transaction {
    constructor(wrappers) {
        this.wrappers = wrappers;
    }
    perform(func) {
        this.wrappers.forEach(wrapper=>wrapper.initialize())
        func.call();
        this.wrappers.forEach(wrapper=>wrapper.close())
    }

}
let transaction = new Transaction([
    {
        initialize() {
            console.log('before1');
        },
        close() {
            console.log('after1');
        }
    },
    {
        initialize() {
            console.log('before2');
        },
        close() {
            console.log('after2');
        }
    }
]);
transaction.perform(setState);

6.4 index.js

class Transaction {
    constructor(wrapper){
        this.wrapper = wrapper;
    }
    perform(func){
        this.wrapper.initialize();
        func.call();
        this.wrapper.close();
    }

}
let batchingStrategy = {
    isBatchingUpdates:false,
    updaters:[],
    batchedUpdates(){
        this.updaters.forEach(updater => {
            updater.component.updateComponent();
        });
    }
}
class Updater{
    constructor(component){
        this.component = component;
        this.pendingStates = [];
    }
    addState(particalState){
        this.pendingStates.push(particalState);
        batchingStrategy.isBatchingUpdates?batchingStrategy.updaters.push(this):this.component.updateComponent();
    }
}
let transaction = new Transaction({
    initialize() {
        batchingStrategy.isBatchingUpdates = true;
    },
    close() {
        batchingStrategy.isBatchingUpdates = false;
        batchingStrategy.batchedUpdates();
    }
});
window.trigger = function(event,name){
    let component = event.target.component;
    transaction.perform(component[name].bind(component,event));
}
class Component{
    constructor(props){
        this.props = props;
        this.$updater = new Updater(this);
    }
    createDOMFromString(domString){
        const div = document.createElement('div');
        div.innerHTML = domString;
        return div.children[0];
    }
    setState(particalState){
        this.$updater.addState(particalState);
    }
    updateComponent(){
        let pendingStates = this.$updater.pendingStates;
        pendingStates.forEach(particalState=>Object.assign(this.state,particalState));
        this.$updater.pendingStates.length = 0;
        let oldElement = this.domElement;
        let newElement = this.renderElement();
        oldElement.parentElement.replaceChild(newElement,oldElement);
    }
    renderElement(){
        let renderString = this.render();
        this.domElement = this.createDOMFromString(renderString);
        this.domElement.component = this;
        return this.domElement;
    }
    mount(container){
        container.appendChild(this.renderElement());
    }
}
class Counter extends Component{
  constructor(props){
      super(props);
      this.state = {number:0};
  }  
  increment(){
   this.setState({number:this.state.number+1});
   console.log(this.state);
   this.setState({number:this.state.number+1});
   console.log(this.state);
   setTimeout(()=>{
    this.setState({number:this.state.number+1});
    console.log(this.state);
    this.setState({number:this.state.number+1});
    console.log(this.state);
   },1000);
  }
  render(){
      return (
        `
        <button id="counter-btn" onclick="trigger(event,'increment')">
         ${this.props.name}:${this.state.number}
        </button>
        `
      )
  }
}

7. Ref

  • Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素
  • 在 React 渲染生命周期时,表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个 defaultValue 属性,而不是 value

7.1 为 dom元素添加Ref

import React from './react';
import ReactDOM from './react-dom';
class Sum extends React.Component{
  constructor(props){
    super(props);
    this.a = React.createRef();//{current:null}
    this.b = React.createRef();//{current:null}
    this.result = React.createRef();//{current:null}
  }
  handleAdd = (event)=>{
    let valueA = this.a.current.value;
    let valueB = this.b.current.value;
    this.result.current.value = valueA+valueB;
  }
  render(){
    return (
      <div>
        <input ref={this.a}/>+<input ref={this.b}/>
        <button onClick={this.handleAdd}>=</button>
        <input ref={this.result}/>
      </div>
    )
  }
}
ReactDOM.render(<Sum/>,document.getElementById('root'));

7.2 为 class 组件添加 Ref

  • 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性
import React from './react';
import ReactDOM from './react-dom';
class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this.input = React.createRef();
  }
  getFocus=()=>{
    this.input.current.focus();
  }
  render() {
    return <input  ref={this.input}/>
  }
}
class Form extends React.Component {
  constructor(props) {
    super(props);
    this.input = React.createRef();
  }
  getFocus = () => {
    this.input.current.getFocus();
  }
  render() {
    return (
      <div>
        <TextInput ref={this.input} />
        <button onClick={this.getFocus}>获得焦点</button>
      </div>
    )
  }
}
ReactDOM.render(<Form />, document.getElementById('root'));

7.3 函数组件添加Ref, useRef

import React from 'react';
import ReactDOM from 'react-dom';
function Counter() {
  const [number, setNumber] = React.useState(0);
  const numberRef = React.useRef();
  const handleClick = () => {
    setNumber(number + 1);
    numberRef.current = number + 1;
    console.log(numberRef.current);
  }
  React.useEffect(() => {

  });
  return (
    <div>
      <p>{number}</p>
      <button onClick={handleClick}>+</button>
    </div>
  )
}
ReactDOM.render(<Counter />, document.getElementById('root'));

7.4 Ref转发

  • 你不能在函数组件上使用 ref 属性,因为他们没有实例

  • Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧

  • Ref 转发允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件

    class Form extends React.Component {
      constructor(props){
          super(props);
          this.input = React.createRef();
      }
      getFocus = () => {
          this.input.current.getFocus();
      }
      render() {
          return (
              <>
                <TextInput ref={this.input}/>
                <button onClick={this.getFocus}>获得焦点</button>
              </>
          );
      }
    }
    // Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
    function TextInput (){
      return <input/>
    }
    

7.4.1 使用forwardRef

class Form extends React.Component {
    constructor(props){
        super(props);
        this.input = React.createRef();
    }
    getFocus = () => {
        this.input.current.focus();
    }
    render() {
        return (
            <>
              <TextInput ref={this.input}/>
              <button onClick={this.getFocus}>获得焦点</button>
            </>
        );
    }
}

const TextInput = React.forwardRef((props,ref)=>(
    <input ref={ref}/>
));

7.4.2 实现forwardRef

function createRef(){
    return {
        current:null
    }
}
class Form extends React.Component {
    constructor(props){
        super(props);
        this.input = createRef();
    }
    getFocus = () => {
        this.input.current.focus();
    }
    render() {
        return (
            <>
              <TextInput myref={this.input}/>
              <button onClick={this.getFocus}>获得焦点</button>
            </>
        );
    }
}
function forwardRef(funcComponent){
    return function(props){
        let ref = props.myref;
        return funcComponent(props,ref);
    }
}
const TextInput = forwardRef((props,ref)=>(
    <input ref={ref}/>
));

8.生命周期

8.1 旧版生命周期

react15

import React, {
    Component,
    useState,
    useImperativeHandle,
    useCallback,
    useMemo,
    useRef,
    useEffect,
    forwardRef,
    useLayoutEffect
} from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component{ // 他会比较两个状态相等就不会刷新视图 PureComponent是浅比较
    static defaultProps = {
      name:'高途'
    };
    constructor(props){
      super();
      this.state = {number:0}
      console.log('1.constructor构造函数')
    }
    componentWillMount(){ // 取本地的数据 同步的方式:采用渲染之前获取数据,只渲染一次
      console.log('2.组件将要加载 componentWillMount');
    }
    componentDidMount(){
      console.log('4.组件挂载完成 componentDidMount');
    }
    handleClick=()=>{
      this.setState({number:this.state.number+1});
    };
    // react可以shouldComponentUpdate方法中优化 PureComponent 可以帮我们做这件事
    shouldComponentUpdate(nextProps,nextState){ // 代表的是下一次的属性 和 下一次的状态
      console.log('5.组件是否更新 shouldComponentUpdate');
      return nextState.number%2;
      // return nextState.number!==this.state.number; //如果此函数种返回了false 就不会调用render方法了
    } //不要随便用setState 可能会死循环
    componentWillUpdate(){
      console.log('6.组件将要更新 componentWillUpdate');
    }
    componentDidUpdate(){
      console.log('7.组件完成更新 componentDidUpdate');
    }
    render(){
      console.log('3.render');
      return (
        <div>
          <p>{this.state.number}</p>
          {this.state.number>3?null:<ChildCounter n={this.state.number}/>}
          <button onClick={this.handleClick}>+</button>
        </div>
      )
    }
  }
  class ChildCounter extends Component{
    componentWillUnmount(){
      console.log('组件将要卸载componentWillUnmount')
    }
    componentWillMount(){
      console.log('child componentWillMount')
    }
    render(){
      console.log('child-render')
      return (<div>
        {this.props.n}
      </div>)
    }
    componentDidMount(){
      console.log('child componentDidMount')
    }
    componentWillReceiveProps(newProps){ // 第一次不会执行,之后属性更新时才会执行
      console.log('child componentWillReceiveProps')
    }
    shouldComponentUpdate(nextProps,nextState){
      return nextProps.n%3==0; //子组件判断接收的属性 是否满足更新条件 为true则更新
    }
  }
ReactDOM.render(<Counter/>, document.getElementById('root'));

// defaultProps
// constructor
// componentWillMount
// render
// componentDidMount
// 状态更新会触发的
// shouldComponentUpdate nextProps,nextState=>boolean
// componentWillUpdate
// componentDidUpdate
// 属性更新
// componentWillReceiveProps newProps
// 卸载
// componentWillUnmount

8.2 新版生命周期

react16

8.2.1 getDerivedStateFromProps

9. Context(上下文)

  • 在某些场景下,你想在整个组件树中传递数据,但却不想手动地在每一层传递属性。你可以直接在 React 中使用强大的contextAPI解决上述问题
  • 在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props

contextapi

9.1 类组件使用

 * 类组件中要想获取ThemeContext.Provider的value属性的值有两种方式
 * 1.给类组件添加属性 static contextType=ThemeContext  this.context获取value值
 * 2.可以使用ThemeContext.Consumer组件获取到value值 Consumer组件的儿子是一个函数,函数的参数就是value值
 * 其实Context实现原理就只是一个共享的变量,仅此而矣
 
import React from 'react';
import ReactDOM from 'react-dom';
let ThemeContext = React.createContext();

class Title extends React.Component {
    static contextType = ThemeContext
    render() {
        return (
            <div style={{ border: `5px solid ${this.context.color}` }}>
                Title
            </div>
        )
    }
}
class Header extends React.Component {
    static contextType = ThemeContext
    render() {
        return (
            <div style={{ border: `5px solid ${this.context.color}` }}>
                Header
                <Title />
            </div>
        )
    }
}
class Content extends React.Component {
    static contextType = ThemeContext
    render() {
        return (
            <div style={{ border: `5px solid ${this.context.color}` }}>
                Content
                <button onClick={() => this.context.changeColor('red')}>变红</button>
                <button onClick={() => this.context.changeColor('green')}>变绿</button>
            </div>
        )
    }
}
class Main extends React.Component {
    static contextType = ThemeContext
    render() {
        return (
            <div style={{ border: `5px solid ${this.context.color}` }}>
                Main
                <Content />
            </div>
        )
    }
}
class Panel extends React.Component {
    state = { color: 'green' }
    changeColor = (color) => {
        this.setState({ color });
    }
    render() {
        let value = { color: this.state.color, changeColor: this.changeColor };
        //Provider提供者,它负责向下层所有的组件提供数据value
        return (
            <ThemeContext.Provider value={value}>
                <div style={{ border: `5px solid ${this.state.color}`, width: 300 }}>
                    Panel
                    <Header />
                    <Main />
                </div>
            </ThemeContext.Provider>
        )
    }
}
ReactDOM.render(<Panel />, document.getElementById('root'));

9.2 函数组件使用

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
let ThemeContext = React.createContext('theme');

class Header extends Component {
    render() {
        return (
             <ThemeContext.Consumer>
                {
                    value => (
                        <div style={{ border: `5px solid ${value.color}`, padding: 5 }}>
                          header
                          <Title />
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class Title extends Component {
    static contextType = ThemeContext;
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    value => (
                       <div style={{border: `5px solid ${value.color}` }}>
                            title
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class Main extends Component {
    static contextType = ThemeContext;
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    value => (
                      <div style={{ border: `5px solid ${value.color}`, margin: 5, padding: 5 }}>
                        main
                        <Content />
                      </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class Content extends Component {
    static contextType = ThemeContext;
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    value => (
                     <div style={{border: `5px solid ${value.color}`, padding: 5 }}>
                        Content
                            <button onClick={() =>value.changeColor('red')} style={{color:'red'}}>红色</button>
                        <button onClick={() => value.changeColor('green')} style={{color:'green'}}>绿色</button>
                     </div>
                    )
                }
            </ThemeContext.Consumer>

        )
    }
}
class Page extends Component {
    constructor() {
        super();
        this.state = { color: 'red' };
    }
    changeColor = (color) => {
        this.setState({ color })
    }
    render() {
        let contextVal = {changeColor: this.changeColor,color:this.state.color };
        return (
            <ThemeContext.Provider value={contextVal}>
                <div style={{margin:'10px', border: `5px solid ${this.state.color}`, padding: 5, width: 200 }}>
                    page
                    <Header />
                    <Main />
                </div>
            </ThemeContext.Provider>

        )
    }
}
ReactDOM.render(<Page />, document.querySelector('#root'));

9.3 context实现

function createContext() {
    let value;
    class Provider extends React.Component {
        constructor(props) {
            super(props);
            value = props.value
            this.state = {};
        }
        static getDerivedStateFromProps(nextProps, prevState) {
            value = nextProps.value;
            return {};
        }
        render() {
            return this.props.children;
        }
    }
    class Consumer extends React.Component {
        constructor(props) {
            super(props);
        }
        render() {
            return this.props.children(value);
        }
    }
    return {
        Provider,
        Consumer
    }
}
let ThemeContext = createContext('theme');

10. 高阶组件

  • 高阶组件就是一个函数,传给它一个组件,它返回一个新的组件
  • 高阶组件的作用其实就是为了组件之间的代码复用
const NewComponent = higherOrderComponent(OldComponent)

10.1 日志组件

10.1.1 手工实现

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
    componentWillMount() {
        this.start = Date.now();
    }
    componentDidMount() {
        console.log((Date.now() - this.start) + 'ms')
    }
    render() {
        return <div>App</div>
    }
}

ReactDOM.render(<App />, document.getElementById('root'));

10.1.2 高阶组件

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
const logger = (WrappedComponent) => {
    class LoggerComponent extends Component {
      componentWillMount(){
          this.start = Date.now();
      }  
      componentDidMount(){
          console.log((Date.now() - this.start)+'ms')
      }
      render () {
        return <WrappedComponent />
      }
    }
    return LoggerComponent;
  }
let Hello = logger(props=><h1>hello</h1>);

ReactDOM.render(<Hello />, document.getElementById('root'));

10.2 多层高阶组件

10.2.1 从localStorage中加载

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
// 接受一个组件,和参数
const fromLocal = (WrappedComponent,name) =>{
    class NewComponent extends Component{
        constructor(){
            super();
            this.state = {value:null};
        }
        componentWillMount(){
            let value = localStorage.getItem(name);
             this.setState({value});
        }
        render(){
            // 
            return <WrappedComponent value={this.state.value}/>
        }
    }
    return NewComponent;
}
// 组件参数来源高阶组件内部
const UserName = ({value})=>(
    <input defaultValue = {value}/>
);
// 高阶组件所需 username,生成组件所需要的props参数
const UserNameFromLocal = fromLocal(UserName,'username');

ReactDOM.render(<UserNameFromLocal />, document.getElementById('root'));

10.2.2 从ajax中加载

import React,{Component} from 'react';
import ReactDOM from 'react-dom';

const fromLocal = (WrappedComponent,name) =>{
    class NewComponent extends Component{
        constructor(){
            super();
            this.state = {id:null};
        }
        componentWillMount(){
            let id = localStorage.getItem(name);
            this.setState({id});
        }
        render(){
            return <WrappedComponent id={this.state.id}/>
        }
    }
    return NewComponent;
}
const fromAjax = (WrappedComponent) =>{
    class NewComponent extends Component{
        constructor(){
            super();
            this.state = {value:{}};
        }
        componentDidMount(){
            fetch(`/${this.props.id}.json`).then(response=>response.json()).then(value=>{
                this.setState({value});
            });
        }
        render(){
            return <WrappedComponent value={this.state.value}/>
        }
    }
    return NewComponent;
}
const UserName = ({value})=>{
  return <input defaultValue = {value.username}/>;
}

const UserNameFromAjax = fromAjax(UserName);
const UserNameFromLocal = fromLocal(UserNameFromAjax,'id');


ReactDOM.render(<UserNameFromLocal />, document.getElementById('root'));

translate.json

{
    "zhangsan": "张三"
}

10.3 属性代理

import React from 'react';
import ReactDOM from 'react-dom';
/**
 * 高阶组件可以实现属性代理,给组件添加额外的属性,以实现特定的逻辑,可以实现逻辑的复用
 * @param {} OldComponent 
 * @returns 
 */
const withLoading = title => (OldComponent) => {
  return class extends React.Component {
    // constructor(props) {
    //   super(props);
    // }
    render() {
      const state = {
        show() {
          console.log('show');
        },
        hide() {
          console.log('hide');
        }
      }
      return (
        <OldComponent {...this.props} {...state} title={title} />
      )
    }
  }
}
@withLoading('加载中...')
class Hello extends React.Component {
  // constructor(props) {
  //   super(props);
  // }
  render() {
    return (
      <div>
        <p>{this.props.title1}</p>
        <button onClick={this.props.show}>显示</button>
        <button onClick={this.props.hide}>隐藏</button>
      </div>
    )
  }
}

const LoadingHello = withLoading('加载中')(Hello);

ReactDOM.render(<LoadingHello title1="标题" />, document.getElementById('root'));

10.4 反向继承

  • 属性代理的时候 返回一个新组件,新组件会渲染老组件二个组件

  • 在这个反向继承当前,我们返回一个新组件,新组件继承自老组件的,只有一个组件

  • 一般来说子组件继承父组件,这个叫正向继承.如是正向的话先执行父亲再执行儿子

  • 高阶组件可以反向继承

  • 假如说你使用一个第三方库,源代码你无法修改,但又想扩展其功能

import React from 'react';
import ReactDOM from 'react-dom';
/**
 * @param {} OldComponent 
 * @returns 
 */
//假如说这是一个第三方组件,你只使用使用修改源码
//这种写法可以拦截生命周期,拦截渲染过程

class Button extends React.Component {
  state = { name: '张三' }
  componentWillMount() {
    console.log('父亲 componentWillMount');
  }
  componentDidMount() {
    console.log('父亲 componentDidMount');
  }
  render() {
    console.log('父亲 render');
    return (
      <button name={this.state.name}>{this.props.title}</button >
    )
  }
}
const wrapper = (OldComponent) => {
  return class extends OldComponent {
    state = { number: 0 }
    componentWillMount() {
      console.log('儿子 componentWillMount');
      super.componentWillMount();
    }
    componentDidMount() {
      console.log('儿子 componentDidMount');
      super.componentDidMount();
    }
    handleClick = () => {
      this.setState({ number: this.state.number + 1 });
    }
    render() {
      console.log('儿子 render');
      let renderElement = super.render();
      let newProps = {
        ...renderElement.props,
        onClick: this.handleClick
      }
      return React.cloneElement(
        renderElement,
        newProps,
        this.state.number
      );
    }
  }
}
let WrappedButton = wrapper(Button);
ReactDOM.render(<WrappedButton title="按钮的标题" />, document.getElementById('root'));


11. render props

  • render-props
  • render prop 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术
  • 具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑
  • render prop 是一个用于告知组件需要渲染什么内容的函数 prop
  • 这也是逻辑复用的一种方式
<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

11.1 使用例子

class MouseTracker 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 onMouseMove={this.handleMouseMove}>
                <h1>移动鼠标!</h1>
                <p>当前的鼠标位置是 ({this.state.x}, {this.state.y})</p>
            </div>
        );
    }
}

11.2 render children

class MouseTracker 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 onMouseMove={this.handleMouseMove}>
               {this.props.children(this.state)}
            </div>
        );
    }
}
ReactDOM.render(< MouseTracker >
{
    props=>(
        <>
          <h1>移动鼠标!</h1>
          <p>当前的鼠标位置是 ({props.x}, {props.y})</p>
        </>
    )
}
</ MouseTracker>, document.getElementById('root'));

11.3 render属性

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
class MouseTracker 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 onMouseMove={this.handleMouseMove}>
               {this.props.render(this.state)}
            </div>
        );
    }
}

ReactDOM.render(< MouseTracker render={params=>(
    <>
      <h1>移动鼠标!</h1>
      <p>当前的鼠标位置是 ({params.x}, {params.y})</p>
    </>
)} />, document.getElementById('root'));

11.4 HOC

class MouseTracker 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 onMouseMove={this.handleMouseMove}>
               {this.props.render(this.state)}
            </div>
        );
    }
}
function withMouse(Component){
 return (
     (props)=><MouseTracker render={mouse=><Component {...props} {...mouse}/>}/>
 )
}
let App = withMouse(props=>(
    <>
      <h1>移动鼠标!</h1>
      <p>当前的鼠标位置是 ({props.x}, {props.y})</p>
    </>
));
ReactDOM.render(<App/>, document.getElementById('root'));

14. 插槽(Portals)

  • Portals 提供了一种很好的方法,将子节点渲染到父组件 DOM 层次结构之外的 DOM 节点。
ReactDOM.createPortal(child, container)
  • 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 片段(fragment)
  • 第二个参数(container)则是一个 DOM 元素

index.html

 <div id="modal-root"></div>

index.js

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import './modal.css';

class Modal extends Component{
    constructor() {
        super();
        this.modal=document.querySelector('#modal-root');
    }
    render() {
        return ReactDOM.createPortal(this.props.children,this.modal);
    }
}
class Page extends Component{
    constructor() {
        super();
        this.state={show:false};
    }
    handleClick=() => {
        this.setState({show:!this.state.show});
    }
    render() {
        return (
            <div>
                <button onClick={this.handleClick}>显示模态窗口</button>
                {
                    this.state.show&&<Modal>
                    <div id="modal" className="modal">
                        <div className="modal-content" id="modal-content">
                                内容
                                <button onClick={this.handleClick}>关闭</button>
                        </div>
                    </div>
                </Modal>
                }
            </div>
        )
    }
}
ReactDOM.render(<Page/>,document.querySelector('#root'));

modal.css

.modal{
    position: fixed;
    left:0;
    top:0;
    right:0;
    bottom:0;
    background: rgba(0,0,0,.5);
    display: block;
}

@keyframes zoom{
    from{transform:scale(0);}
    to{transform:scale(1);}
}

.modal .modal-content{
    width:50%;
    height:50%;
    background: white;
    border-radius: 10px;
    margin:100px auto;
    display:flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    animation: zoom .6s;
}

image.png

15. 性能优化

15.1 使用React.Fragment

  • 使用React.Fragment来避免向 DOM 添加额外的节点
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Users extends React.Component {
  render() {
    return (
      <React.Fragment>
        <div>用户1</div>
        <div>用户2</div>
      </React.Fragment>
    );
  }
}
ReactDOM.render(<Users />, document.querySelector('#root'));

15.2. 使用 React.Lazy 延迟加载组件

  • React.Lazy帮助我们按需加载组件,从而减少我们应用程序的加载时间,因为只加载我们所需的组件。
  • React.lazy 接受一个函数,这个函数内部调用 import() 动态导入。它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。
  • React.Suspense 用于包装延迟组件以在加载组件时显示后备内容。
import React, { Component,lazy, Suspense } from 'react'
import ReactDOM from 'react-dom';
import Loading from './components/Loading';
const AppTitle  = lazy(()=>import(/* webpackChunkName: "title" */'./components/Title'))

class App extends Component{
    state = {visible:false}
    show = ()=>{
        this.setState({visible:true});
    }
    render() {
        return (
            <>
             {this.state.visible&&(
                 <Suspense fallback={<Loading/>}>
                    <AppTitle/>
                 </Suspense>
             )}
             <button onClick={this.show}>加载</button>
            </>
        )
    }
}
ReactDOM.render(<App />, document.querySelector('#root'));

15.3. 错误边界(Error Boundaries)

  • 如果当一个组件异步加载下载js文件时,网络错误,无法下载 js 文件
  • Suspense 无法处理这种错误情况, 在 react 中有一个 错误边界 (Error Boundaries)的概念,用来解决这种问题,它是利用了 react 生命周期的 componetDidCatch 方法来处理
  • 有两种方式,一种是 生命周期 componentDidCatch 来处理错误,还有一种 是 静态方法 static getDerivedStateFromError 来处理错误,
  • 请使用static getDerivedStateFromError()渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
import React, { Component,lazy, Suspense } from 'react'
import ReactDOM from 'react-dom';
import Loading from './components/Loading';
const AppTitle  = lazy(()=>import(/* webpackChunkName: "title" */'./components/Title'))

class App extends Component{
    state = {visible:false,isError: false}
    show = ()=>{
        this.setState({visible:true});
    }

    static getDerivedStateFromError(error) {
      return { isError: true };
    }

    componentDidCatch (err, info) {
      console.log(err, info)
    }
    render() {
        if (this.state.isError) {
            return (<div>error</div>)
        }
        return (
            <>
             {this.state.visible&&(
                 <Suspense fallback={<Loading/>}>
                    <AppTitle/>
                 </Suspense>
             )}
             <button onClick={this.show}>加载</button>
            </>
        )
    }
}
ReactDOM.render(<App />, document.querySelector('#root'));

15.4. PureComponent

  • 当一个组件的propsstate变更,React 会将最新返回的元素与之前渲染的元素进行对比,以此决定是否有必要更新真实的 DOM,当它们不相同时 React 会更新该 DOM。
  • 如果渲染的组件非常多时可以通过覆盖生命周期方法 shouldComponentUpdate 来进行优化
  • shouldComponentUpdate 方法会在重新渲染前被触发。其默认实现是返回 true,如果组件不需要更新,可以在shouldComponentUpdate中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及之后的操作
  • PureComponent通过prop和state的浅比较来实现shouldComponentUpdate

15.4.1 App.js

import React from 'react';
import {Button,message} from 'antd';
import PureComponent from './PureComponent';
export default class App extends PureComponent{
  state = {
    title:'计数器',
    number:0
  }
  add = ()=>{
    this.setState({number:this.state.number+parseInt(this.amount.value)});
  }
  render(){
    console.log('App render');
    return (
      <div>
        <Title2 title={this.state.title}/>
        <Counter number={this.state.number}/>
        <input ref={inst=>this.amount = inst}/>
        <button onClick={this.add}>+</button>
      </div>
    )
  }
}
class Counter extends PureComponent{
  render(){
    console.log('Counter render');
    return (
     <p>{this.props.number}</p>
    )
  }
}
//类组件可以用继承
class Title extends PureComponent{
  render(){
    console.log('Title render');
    return (
     <p>{this.props.title}</p>
    )
  }
}
//函数组件可以和memo
const Title2 = React.memo(props=>{
  console.log('Title2 render');
  return  <p>{props.title}</p>;
});

//memo的实现
function memo(func){
  class Proxy extends PureComponent{
    render(){
      return func(this.props);
    }
  }
  return Proxy;
}
//memo的另一种实现 接收一个函数组件
function memo2(Func){
  class Proxy extends PureComponent{
    render(){
      return <Func {...this.props}/>
    }
  }
  return Proxy;
}

15.4.2 PureComponent

import React from 'react';
function shallowEqual(obj1,obj2){
    if(obj1 === obj2){
        return true;
    }
    if(typeof obj1 != 'object' || obj1 === null ||typeof obj2 != 'object' || obj2 === null ){
        return false;
    }
    let keys1 = Object.keys(obj1);
    let keys2 = Object.keys(obj2);
    if(keys1.length != keys2.length){
        return false;
    }
    for(let key of keys1){
        if(!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]){
            return false;
        }
    }
    return true;
}
export default class PureComponent extends React.Component{
    isPureReactComponent = true
    shouldComponentUpdate(nextProps,nextState){
        return !shallowEqual(this.props,nextProps)||!shallowEqual(this.state,nextState)
    }
}

15.6 减少渲染次数

  • 把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
  • 把创建函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
import React from 'react';
import ReactDOM from 'react-dom';
/**
 * 1.让Child支持memo
 */
function Child({ data, handleClick }) {
  console.log('Child render');
  return (
    <button onClick={handleClick}>{data.number}</button>
  )
}
// let MemoChild = React.memo(Child);
function App() {
  console.log('App render');
  const [name, setName] = React.useState('zhufeng');
  const [number, setNumber] = React.useState(0);
  let data = React.useMemo(() => ({ number }), [number]);
  let handleClick = React.useCallback(() => setNumber(number + 1), [number]);
  return (
    <div>
      <input type="text" value={name} onChange={(event) => setName(event.target.value)} />
      <Child data={data} handleClick={handleClick} />
    </div>
  )
}
ReactDOM.render(<App />, document.getElementById('root'));

16. React Hooks

  • Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
  • 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以在现有的函数组件中使用 Hook
  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。

16.1 解决的问题

  • 在组件之间复用状态逻辑很难,可能要用到render props和高阶组件,React 需要为共享状态逻辑提供更好的原生途径,Hook 使你在无需修改组件结构的情况下复用状态逻辑
  • 复杂组件变得难以理解,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
  • 难以理解的 class,包括难以捉摸的this

16.2. 注意事项

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用

16.4. useState

  • useState 就是一个 Hook

  • 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state

  • useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并

  • useState 唯一的参数就是初始 state

  • 返回一个 state,以及更新 state 的函数

    • 在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
    • setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列
const [state, setState] = useState(initialState);

16.5. useReducer

  • useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
  • 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等

16.5.1 基本用法

const [state, dispatch] = useReducer(reducer, initialArg, init);
const initialState = 0;

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {number: state.number + 1};
    case 'decrement':
      return {number: state.number - 1};
    default:
      throw new Error();
  }
}
function init(initialState){
    return {number:initialState};
}
function Counter(){
    const [state, dispatch] = useReducer(reducer, initialState,init);
    return (
        <>
          Count: {state.number}
          <button onClick={() => dispatch({type: 'increment'})}>+</button>
          <button onClick={() => dispatch({type: 'decrement'})}>-</button>
        </>
    )
}

16.5.2. useContext

  • 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
  • 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定
  • 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值
  • useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
  • useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context

const CounterContext = React.createContext();

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {number: state.number + 1};
    case 'decrement':
      return {number: state.number - 1};
    default:
      throw new Error();
  }
}
function Counter(){
  let {state,dispatch} = useContext(CounterContext);
  return (
      <>
        <p>{state.number}</p>
        <button onClick={() => dispatch({type: 'increment'})}>+</button>
        <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      </>
  )
}
function App(){
    const [state, dispatch] = useReducer(reducer, {number:0});
    return (
        <CounterContext.Provider value={{state,dispatch}}>
            <Counter/>
        </CounterContext.Provider>
    )

}

17. effect

  • 在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
  • 使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道
  • useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API
  • 该 Hook 接收一个包含命令式、且可能有副作用代码的函数
useEffect(didUpdate);

17.1 通过class实现修标题

class Counter extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        number: 0
      };
    }

    componentDidMount() {
        document.title = `你点击了${this.state.number}次`;
    }

    componentDidUpdate() {
        document.title = `你点击了${this.state.number}次`;
    }

    render() {
      return (
        <div>
          <p>{this.state.number}</p>
          <button onClick={() => this.setState({ number: this.state.number + 1 })}>
            +
          </button>
        </div>
      );
    }
  }

在这个 class 中,我们需要在两个生命周期函数中编写重复的代码,这是因为很多情况下,我们希望在组件加载和更新时执行同样的操作。我们希望它在每次渲染之后执行,但 React 的 class 组件没有提供这样的方法。即使我们提取出一个方法,我们还是要在两个地方调用它。useEffect会在第一次渲染之后和每次更新之后都会执行

17.2 通过effect实现

import React,{Component,useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
function Counter(){
    const [number,setNumber] = useState(0);
    // 相当于 componentDidMount 和 componentDidUpdate:
    useEffect(() => {
        // 使用浏览器的 API 更新页面标题
        document.title = `你点击了${number}次`;
    });
    return (
        <>
            <p>{number}</p>
            <button onClick={()=>setNumber(number+1)}>+</button>
        </>
    )
}
ReactDOM.render(<Counter />, document.getElementById('root'));

每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect 属于一次特定的渲染。

17.3 跳过 Effect 进行性能优化

  • 如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可
  • 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行
function Counter(){
  const [number,setNumber] = useState(0);
  // 相当于componentDidMount 和 componentDidUpdate
  useEffect(() => {
     console.log('开启一个新的定时器')
     const $timer = setInterval(()=>{
      setNumber(number=>number+1);
     },1000);
  },[]);
  return (
      <>
          <p>{number}</p>
      </>
  )
}

17.4 清除副作用

  • 副作用函数还可以通过返回一个函数来指定如何清除副作用
  • 为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染,则在执行下一个 effect 之前,上一个 effect 就已被清除
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function Counter() {
    const [number, setNumber] = useState(0);
    useEffect(() => {
        console.log('开启一个新的定时器')
        const $timer = setInterval(() => {
            setNumber(number => number + 1);
        }, 1000);
        return () => {
            console.log('销毁老的定时器');
            clearInterval($timer);
        }
    });
    return (
        <>
            <p>{number}</p>
        </>
    )
}
function App() {
    let [visible, setVisible] = useState(true);
    return (
        <div>
            {visible && <Counter />}
            <button onClick={() => setVisible(false)}>stop</button>
        </div>
    )
}
ReactDOM.render(<App />, document.getElementById('root'));

18 useRef

  • useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)
  • 返回的 ref 对象在组件的整个生命周期内保持不变
const refContainer = useRef(initialValue);
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Parent() {
    let [number, setNumber] = useState(0);
    return (
        <>
            <Child />
            <button onClick={() => setNumber({ number: number + 1 })}>+</button>
        </>
    )
}
let input;
function Child() {
    const inputRef = useRef();
    console.log('input===inputRef', input === inputRef);
    input = inputRef;
    function getFocus() {
        inputRef.current.focus();
    }
    return (
        <>
            <input type="text" ref={inputRef} />
            <button onClick={getFocus}>获得焦点</button>
        </>
    )
}
ReactDOM.render(<Parent />, document.getElementById('root'));

19. forwardRef

  • 将ref从父组件中转发到子组件中的dom元素上
  • 子组件接受props和ref作为参数
function Child(props,ref){
  return (
    <input type="text" ref={ref}/>
  )
}
Child = forwardRef(Child);
function Parent(){
  let [number,setNumber] = useState(0); 
  const inputRef = useRef();
  function getFocus(){
    inputRef.current.value = 'focus';
    inputRef.current.focus();
  }
  return (
      <>
        <Child ref={inputRef}/>
        <button onClick={()=>setNumber({number:number+1})}>+</button>
        <button onClick={getFocus}>获得焦点</button>
      </>
  )
}

20. useImperativeHandle

  • useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值
  • 在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用
function Child(props,ref){
  const inputRef = useRef();
  useImperativeHandle(ref,()=>(
    {
      focus(){
        inputRef.current.focus();
      }
    }
  ));
  return (
    <input type="text" ref={inputRef}/>
  )
}
Child = forwardRef(Child);
function Parent(){
  let [number,setNumber] = useState(0); 
  const inputRef = useRef();
  function getFocus(){
    console.log(inputRef.current);
    inputRef.current.value = 'focus';
    inputRef.current.focus();
  }
  return (
      <>
        <Child ref={inputRef}/>
        <button onClick={()=>setNumber({number:number+1})}>+</button>
        <button onClick={getFocus}>获得焦点</button>
      </>
  )
}

21. useLayoutEffect

  • 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
  • 可以使用它来读取 DOM 布局并同步触发重渲染
  • 在浏览器执行绘制之前useLayoutEffect内部的更新计划将被同步刷新
  • 尽可能使用标准的 useEffect 以避免阻塞视图更新
function LayoutEffect() {
    const [color, setColor] = useState('red');
    useLayoutEffect(() => {
        alert(color);
    });
    useEffect(() => {
        console.log('color', color);
    });
    return (
        <>
            <div id="myDiv" style={{ background: color }}>颜色</div>
            <button onClick={() => setColor('red')}>红</button>
            <button onClick={() => setColor('yellow')}>黄</button>
            <button onClick={() => setColor('blue')}>蓝</button>
        </>
    );
}

22. 自定义 Hook

  • 有时候我们会想要在组件之间重用一些状态逻辑
  • 自定义 Hook 可以让你在不增加组件的情况下达到同样的目的
  • Hook 是一种复用状态逻辑的方式,它不复用 state 本身
  • 事实上 Hook 的每次调用都有一个完全独立的 state
  • 自定义 Hook 更像是一种约定,而不是一种功能。如果函数的名字以 use 开头,并且调用了其他的 Hook,则就称其为一个自定义 Hook

22.1.自定义计数器

function useNumber(){
  const [number,setNumber] = useState(0);
  useEffect(() => {
     console.log('开启一个新的定时器')
     const $timer = setInterval(()=>{
      setNumber(number+1);
     },1000);
     return ()=>{
      console.log('销毁老的定时器')
         clearInterval($timer);
     }
  });
  return number;
}
function Counter1(){
  let number1 = useNumber();
  return (
      <>
          <p>{number1}</p>
      </>
  )
}
function Counter2(){
  let number = useNumber();
  return (
      <>
          <p>{number}</p>
      </>
  )
}
function App(){
  return <><Counter1/><Counter2/></>
}

22.2 useParams

  • 获取路由中的params

22.2.1 老版

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

function Post({ match }) {
  let { title } = match.params;
  return <div>{title}</div>;
}

ReactDOM.render(
  <Router>
    <div>
      <Switch>
        <Route path="/post/:title" component={Post} />
      </Switch>
    </div>
  </Router>,
  document.getElementById("root")
);

22.2.2.新版

import React from "react";
import ReactDOM from "react-dom";
+import { BrowserRouter as Router, Route, Switch, useParams } from "react-router-dom";

+function Post() {
+  let { title } = useParams();
+  return <div>{title}</div>;
+}

ReactDOM.render(
  <Router>
    <div>
      <Switch>
+        <Route path="/post/:title"><Post /></Route>
      </Switch>
    </div>
  </Router>,
  document.getElementById("root")
);

22.3.useHistory

  • 可以返回上一个网页

22.3.1 老版

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Switch, useHistory } from "react-router-dom";

function Post({ match, history }) {
  let { title } = match.params;
  return (
    <div>
      {title}
      <hr />
      <button type="button" onClick={() => history.goBack()}>
        回去
      </button>
    </div>
  );
}
function Home({ history }) {
  return (
    <>
      <button type="button" onClick={() => history.push("/post/hello")}>
        title
      </button>
    </>
  )
}
ReactDOM.render(
  <Router>
    <div>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/post/:title" component={Post} />
      </Switch>
    </div>
  </Router>,
  document.getElementById("root")
);

22.3.2 新版

import React from "react";
import ReactDOM from "react-dom";
+import { BrowserRouter as Router, Route, Switch, useParams, useHistory } from "react-router-dom";

function Post() {
+  let { title } = useParams();
+  let history = useHistory();
  return (
    <div>
      {title}
      <hr />
+      <button type="button" onClick={() => history.goBack()}>
+        回去
+      </button>
    </div>
  );
}
function Home() {
+  let history = useHistory();
  return (
    <>
+      <button type="button" onClick={() => history.push("/post/hello")}>
+        title
+      </button>
    </>
  )
}
ReactDOM.render(
  <Router>
    <div>
      <Switch>
+        <Route exact path="/" component={Home} />
+        <Route path="/post/:title" component={Post} />
      </Switch>
    </div>
  </Router>,
  document.getElementById("root")
);