Skip to content

扩展及最新特性

setState

更新状态有两种方式, 分别为对象式和函数式

1. 对象式

js
setState(stateChange, [callback]);
  • stateChange 为状态改变对象
  • callback 是可选的回调函数, 在状态更新完毕, 界面也更新(render)后才被调用
jsx
state = { count: 0 };
add = () => {
  this.setState({ count: this.state.count + 1 }, () => {
    console.log(this.state.count); //1
  });
  console.log(this.state.count); //0
};

this.setState 后续更新状态是异步操作, 在回调函数中可以拿到更新完的状态值

2. 函数式

js
setState(updater, [callback]);
  • updater 为返回 stateChange 对象的函数, 它可以接收到 stateprops
  • callback 是可选的回调函数, 在状态更新完毕, 界面也更新(render)后才被调用
jsx
state = { count: 0 };
add = () => {
  this.setState((state, props) => {
    return { count: state.count + props.step };
  });
};

使用规则

  • 新状态不依赖原状态, 使用对象式更为简便
  • 新状态依赖原状态, 使用函数式更简便

Hooks

Hook 是 React 16.8.0 增加的新特性,让我们能在函数式组件中使用 state 和其他特性

State Hook

jsx
const [xxx, setXxx] = React.useState(initValue);
  • initValue 是第一次初始化指定的值, 并在内部缓存
  • 返回值:包含 2 个元素的数组,分别为状态值状态更新函数
jsx
const [count, setCount] = React.useState(0);
const [name, setName] = React.useState("Tom");

function add() {
  setCount(count + 1);
  //   函数式更新方法
  //   setCount((count) => count + 1);
  setName("Jack");
}

有两种更新方式, 当有异步需求时使用函数式, 可以获取到之前状态值

  • 更新对象数组
jsx
const [arr, setArr] = useState([]);
setArr([1, ...arr]);

const [obj, setObj] = useState({});
setObj({ a: 1, ...obj });

Effect Hook

在函数式组件中能执行副作用操作(模拟生命周期钩子)

  • 语法
jsx
React.useEffect(() => {
  // 执行任何副作用操作(请求数据、开启定时器...)
  return () => {
    // 组件卸载之前的操作(清除定时器等)
  };
}, [stateValue]); //可以不写、传[]、传[xxx, xxx, ...]

第二个参数的三种状态

  1. 不写时, 监听所有状态的改变

    jsx
    const [count, setCount] = useState(0);
    const [name, setName] = useState("Jack");
    React.useEffect(() => {
      // 初次挂载会触发
      // 每次只要 count 或 name 状态改变更新, 都会触发
      console.log("状态更新了");
      return () => {
        // ...
      };
    });
  2. 传 [] 表示不监听任何状态更新

    jsx
    const [count, setCount] = useState(0);
    const [name, setName] = useState("Jack");
    React.useEffect(() => {
      // 只有初次挂载会触发
      console.log("状态更新了");
      return () => {
        // ...
      };
    }, []);
  3. 传具体参数表示监听具体状态的改变

    jsx
    const [count, setCount] = useState(0);
    const [name, setName] = useState("Jack");
    React.useEffect(() => {
      // 初次挂载会触发
      // 每次只要且是 name 的状态改变更新, 才会触发
      console.log("状态更新了");
      return () => {
        // ...
      };
    }, [name]);

Ref Hook

类似于类组件中 React.createRef()

jsx
function Demo() {
  const myRef = React.useRef();

  function show() {
    console.log(myRef.current.value);
  }

  return (
    <div>
      <input type="text" ref={myRef} />
      <button onClick={show}>展示数据</button>
    </div>
  );
}

Fragment

  • Fragment 标签本身不会被渲染成一个真实 DOM 标签,类似 Vuetemplate
  • Fragment 标签可以传递 key 属性,遍历时候可用
jsx
import React, { Component, Fragment } from "react";

export default class Demo extends Component {
  render() {
    return (
      <Fragment key={1}>
        <input type="text" />
      </Fragment>
    );
  }
}

Context

一种组件间通信方式,常用于祖组件后代组件间通信

上下文容器

js
// context.js
import React from "react";
// 创建Context容器对象
export const MyContext = React.createContext();
export const { Provider, Consumer } = MyContext;

父组件

jsx
import React, { Component } from "react";
import { Provider } from "./context.js";

export default class A extends Component {
  state = { name: "tony", age: 29 };
  render() {
    const { name, age } = this.state;
    return (
      <div className="parent">
        <h2>A组件</h2>
        {/* 用 Provider 包裹子组件, 通过 value 向后代组件传值 */}
        <Provider value={{ name, age }}>
          <B />
        </Provider>
      </div>
    );
  }
}

子组件

jsx
class B extends Component {
  render() {
    return (
      <div className="child">
        <h2>B组件</h2>
        <C />
      </div>
    );
  }
}

孙组件

  • 类式组件写法
jsx
import { MyContext } from "./context.js";

class C extends Component {
  // 声明接收context
  static contextType = MyContext;
  render() {
    return (
      <div className="grand">
        <h2>C组件</h2>
        <h4>A组件中接收的姓名:{this.context.name}</h4>
      </div>
    );
  }
}
  • 函数式写法
jsx
import { Consumer } from "./context.js";

function C() {
  return (
    <div className="grand">
      <h2>C组件</h2>
      <h4>
        A组件中接收的姓名:
        <Consumer>{(value) => `${value.name};年龄是${value.age}`}</Consumer>
      </h4>
    </div>
  );
}

组件优化

两个问题导致组件更新效率低

  • 只要调用 setState() ,即使没有修改状态,组件也会重新 render()
  • 只要父组件重新渲染,即使子组件没有使用父组件的状态,也会重新 render()

原因: shouldComponentUpdate() 钩子默认总是返回 true

期望效果: 只有组件的 stateprops 的数据发生改变时才重新渲染

解决

使用 PureComponent ,它重写了 shouldComponentUpdate() , 只有 stateprops 数据有变化才返回 true

jsx
import React, { PureComponent } from 'react'

class Demo extends PureComponent {
  ...
  addStu = () => {
    // 不会渲染
    const { stus } = this.state
    stus.unshift('小刘')
    this.setState({ stus })

    // 重新渲染
    const { stus } = this.state
    this.setState({ stus: ['小刘', ...stus] })
  }
  ...
}

TIP

  • 它只是进行 state 和 props 数据的浅比较, 如果只是数据对象内部数据变了, 返回 false。即对于引用数据类型,比较的是地址引用
  • 不要直接修改 state 数据, 而是要产生新数据

render props

将子组件通过 render() 回调传给父组件, 父组件内部定义占位形成嵌套,类似于 vue 中的插槽

jsx
import React, { Component } from "react";

export default class Parent extends Component {
  render() {
    return (
      <div>
        <h3>父组件</h3>
        {/* 通过 render 回调将 B 组件传给 A组件 */}
        <A render={(name) => <B name={name} />} />
      </div>
    );
  }
}
// A组件
class A extends Component {
  state = { name: "tom" };
  render() {
    const { name } = this.state;
    return (
      <div>
        <h3>A组件</h3>
        {/* 占位, 调用 render() 得到 B组件,render内定义要传的参数 */}
        {this.props.render(name)}
      </div>
    );
  }
}
// B组件
class B extends Component {
  render() {
    return (
      <div>
        <h3>B组件,{this.props.name}</h3>
      </div>
    );
  }
}

错误边界

  • 只能捕获后代组件生命周期产生的错误,不捕获自己组件其他组件在合成事件、定时器中产生的错误
  • 只在生产环境(项目上线)起效

子组件(抛出错误)

jsx
export default class Child extends Component {
  state = {
    // 故意传递一个字符串,引起错误
    users: "abc",
  };
  render() {
    return (
      <div>
        {this.state.users.map((item) => {
          return <p key={item.id}>{item.name}</p>;
        })}
      </div>
    );
  }
}

父组件(捕获异常)

jsx
import React, { Component } from "react";
import Child from "./Child";

export default class Parent extends Component {
  state = {
    hasError: "", //用于标识子组件是否产生错误
  };

  // 当子组件出现错误,会触发调用,并携带错误信息
  static getDerivedStateFromError(error) {
    // render 之前触发; 返回新的 state
    return { hasError: error };
  }
  // 子组件产生错误时调用该钩子
  componentDidCatch(error, info) {
    console.log(error, info);
    console.log("此处统计错误,反馈给服务器");
  }

  render() {
    return (
      <div>
        <h2>Parent组件</h2>
        {this.state.hasError ? <h2>网络不稳定,稍后再试</h2> : <Child />}
      </div>
    );
  }
}