React 之JSX、元素、生命周期、setState、props、HOC

1,327 阅读13分钟

友情提示:

  • 代码演示默认是 React V17 版本 ReactDOM.render 模式,特殊的会说明。
  • 以下代码 render 方法不是应该只执行一次,为什么 debugger 时,render 执行多次?
class App extends React.Component {
  render(){
    debugger;
    console.log('render');
    return <div>ddd</div>
  }
}
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

官网严格模式给出答案,仅开发模式开启。

好处:识别不安全的生命周期;关于使用过时字符串 ref API 的警告;关于使用废弃的 findDOMNode 方法的警告;检测意外的副作用;检测过时的 context API 等;

JSX

JSX 是 JavaScript 语法扩展,v16 版本经过 bable 编译后就是 React.createElement() 调用。

例如:const ele = <div>hello</div>; 经过编译后转为

var ele = React.createElement("div", null, "hello");

babel在线查看

注意:react 17 新的转换, 是工具自动从 React 的 package 中引入新的入口函数并调用

(import {jsx as _jsx} from 'react/jsx-runtime';)。

JSX 语法

  • {} 包裹变量、表达式、函数等;
  • HTML类型标签小写,组件类型标签首字母要大写;
  • 所有标签要闭合;
  • 属性用小驼峰;

使用例子:

import React from 'react';

function Hello() {
  return <div>hello func component</div>;
}

class App extends React.Component {
  flag = false;
  sayAge = () => <div>{1 + 3}</div>;

  showApp = () => {
    const eleJSX = (
      <div className="test">
        <div style={{ backgroundColor: 'red', fontSize: '40px' }}>1</div>
        <>
          <div>2 fragment</div>
        </>
        3 文本
        <Hello />
        {this.sayAge()}
        {this.flag ? 'flag is true' : <div>flag is false</div>}
      </div>
    );
    console.log(eleJSX);
    return eleJSX;
  };
  render() {
    return this.showApp();
  }
}

export default App;

JSX 好处

  • 编写方便,直观,提升开发效率;
  • 抽象 React 元素的创建,使得编写组件简单;
  • 防止注入攻击;

React 元素

描述屏幕上看到的内容,是构成 React 应用最小单元,实际上就是一个 JS 对象。

前面代码返回的 ReactElement 格式如下:

image.png

创建方式

  • JSX 语法

  • React.createElement(type, config, children)

  • React.cloneElement(element, config, children)

元素渲染

要想 React 元素呈现到浏览器中,我们需要一个容器,一般是 index.html 中有<div id="root"></div> ,通过ReactDOM.render 方法挂到容器里,ReactDOM.render() 通常只会调用一次,初始化时,将元素挂到容器内,更新时,React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会更新需要更新的部分。

组件 UI = f(data)

组件分为类组件和函数组件:

类组件内部通过constructClassInstance 方法内执行 new 调用,

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      num: 0
    };
  }
  changeNum = () => {
    this.setState({
      num: Math.random()
    });
  };
  render() {
    return (
      <div>
        <Hello num={this.state.num} />
        <button onClick={this.changeNum}>click</button>
      </div>
    );
  }
}

函数组件内部通过 renderWithHooks 方法内 函数执行

function Hello(props) {
  return <div>hello func component{props.num}</div>;
}

事件

  • react 事件和原生事件区别(形态上看 驼峰 函数 不加括号 )

在 React 中不能使用 return false 的方式阻止事件的默认行为,必须要显式的调用事件对象的 preventDefault 方法来阻止事件的默认行为。

  • 处理方式和传参
1、箭头函数 每次 render ,重新生成处理函数 
<button onClick={(event) => {this.handleClick(event, 'dxx')}}>click me</button>
2、render 中 bind 绑定,每次 render ,重新生成处理函数 
<button onClick={this.handleClick.bind(this, 'dxx')}>click me</button>
3、在构造函数中 bind 绑定,多个事件比较繁琐
class App extends React.Component {
    constructor () {
        this.handleClick = this.ha	ndleClick.bind(this, 'dxx');
    }
    handleClick = function () {}
    render () {
        return (
            <button onClick={this.handleClick}>click me</button>
        )
    }
}
4、属性是箭头函数, 不需要收到绑定 this ,没有函数重新创建问题
class App extends React.Component {
    handleClick = (event) => {}
    render () {
        return (
            <button onClick={this.handleClick}>click me</button>
        )
    }
}
事件写法
onClick ={ this.method } / method = () => {}
onClick ={ this.method } / constructor 中 this.method= this.method.bind(this)
onClick = { this.method.bind(this) }
onClick = { () => { this.method() } } / /首先 onClick 值为用大括号包裹一个函数(这个函数由里面箭头函数执行返回一个函数),

通信

父组件向子组件

通过 props 传递

function  Child(props){
return <div>{props.title}</div>
}
class App extends React.Component {
  render(){
    return <Child title="dxx"/>
  }
}

子组件向父组件

父组件中 props 传递回调函数给子组件,子组件调用回调函数,将参数传给父组件

function Child(props) {
  const { title, setName } = props;
  return (
    <div>
      <p>{title}</p>
      <button onClick={() => setName('child')}>click</button>
    </div>
  );
}
class App extends React.Component {
  setName = p => {
    console.log(p);// child
  };
  render() {
    return <Child title="dxx" setName={this.setName} />;
  }
}

跨层级组件

通过 context 共享上下文通信

版本提供者消费者
v16.3 之前getChildContext 对象、 static childContextTypes对象结构this.context拿到、static contextTypes结构
v16.3 之后context.Provider(context: createContext 创建) 中的 vaule 属性1、context.Consumer (render props 方式);
2、this.contextstatic contextType获取;3、函数组件通过 useContext`;
const Theme = {
  theme1: {
    color: 'red',
    background: 'green'
  },
  theme2: {
    color: 'white',
    background: 'black'
  }
};

const ThemeContext = createContext(Theme);

function Child(props) {
  const context = useContext(ThemeContext);
  return <div style={{ color: context.color, backgroundColor: context.background }}>child</div>;
}
class Son extends React.Component {
  constructor(props, context) {
    super(props);
  }
  // render() {
  //   return (
  //     <ThemeContext.Consumer>
  //       {context => {
  //         return (
  //           <>
  //             <div style={{ color: context.color, backgroundColor: context.background }}>son</div>
  //           </>
  //         );
  //       }}
  //     </ThemeContext.Consumer>
  //   );
  // }

  static contextType = ThemeContext;
  render() {
    return <div style={{ color: this.context.color, backgroundColor: this.context.background }}>son</div>;
  }
}

class Fathter extends React.Component {
  render() {
    return (
      <div>
        <Child />
        <Son />
      </div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value={Theme.theme2}>
        <Fathter />
      </ThemeContext.Provider>
    );
  }
}

相邻组件

状态提升,在它们父组件中管理

const defaultState = {
  value: 0
};
function reducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return { ...state, value: state.value + 1 };
    case 'REDUCE':
      return { ...state, value: state.value - 1 };
    default:
      throw new Error();
  }
}

const Context = createContext(null);

function FirstChild() {
  const AppContext = useContext(Context);

  return (
    <div>
      <button
        onClick={() => {
          AppContext.dispatch({ type: 'ADD' });
        }}
      >
        ADD
      </button>
      <button
        onClick={() => {
          AppContext.dispatch({ type: 'REDUCE' });
        }}
      >
        REDUCE
      </button>
    </div>
  );
}

function SecondChild() {
  const AppContext = useContext(Context);

  return <div>{AppContext.state.value}</div>;
}

function App() {
  const [state, dispatch] = useReducer(reducer, defaultState);

  return (
    <Context.Provider value={{ state, dispatch }}>
      <FirstChild />
      <SecondChild />
    </Context.Provider>
  );
}

发布订阅模式

class EventEmmiter {
  es = {};

  on(eventName, cb, once = false) {
    if (!this.es[eventName]) {
      this.es[eventName] = [];
    }
    this.es[eventName].push({ cb, once });
  }
  emit(eventName, ...args) {
    const listerns = this.es[eventName] || [];
    for (let i = 0; i < listerns.length; i++) {
      const { cb, once } = listerns[i];

      cb.apply(this, args);

      if (once) {
        listerns.splice(i--, 1);
      }
    }
  }
  off(eventName, cb) {
    if (eventName === undefined) {
      this.es = {};
    } else {
      if (cb === undefined) {
        delete this.es[eventName];
      } else {
        const listerns = this.es[eventName] || [];
        for (let i = 0; i < listerns.length; i++) {
          if (listerns[i].cb === cb) {
            listerns.splice(i--, 1);
          }
        }
      }
    }
  }
  once(eventName, cb) {
    this.on(eventName, cb, true);
  }
}

框架

组件的生命周期

React 15 生命周期

图片来源网上

image.png

首次渲染,执行 Mounting 列的函数;

更新渲染,分 props 触发,和 state 触发,props 多一个(componentWillReceiveProps)执行,

父组件导致组件重新渲染,即使 props 没改变,也会调用 componentWillReceiveProps;

卸载,执行 componentWillUnmount,

两种情况触发:1、组件从父组件移除;2、组件设置了 key,父组件在 render 过程中发现 key 和上次不一致;

React 16 生命周期

image.png

image.png

v16 相比 v15

去掉 v15 的 componentWillMountcomponentWillReceivePropscomponentWillUpdate

增加 getDerivedStateFromPropsgetSnapshotBeforeUpdate

v16.4 让 getDerivedStateFromProps 在 render 前都会执行;

React 生命周期去掉的函数都是 render 阶段的,升级生命周期函数是为了更好适配 Fiber 架构

render() 把渲染的内容返回出来,不会操作真实的 DOM

v16 render() 返回做了改进,允许返回数组和字符串(之前必须返回单个元素)

每个生命周期处理什么

生命周期函数处理
constructor执行一次,初始化工作(state、事件等)
static getDerivedStateFromProps(nextProps, prevState)代替 componentWillReceiveProps;将 props 映射 state;返回值合并到 state 中,作为 shouldComponentUpdate 第二个参数 newState,用来判断是否渲染;
getSnapshotBeforeUpdate(prevProps, preState)记录更新前的信息,传递给 componentDidUpdate 第三个参数
componentDidUpdate(prevProps, preState, snapshot)DOM 已更新,获取更新后的信息
componentDidMountDOM 操作;数据服务请求;
shouldComponentUpdate(newProps,newState,newContext)性能优化
componentWillUnMount清除,销毁工作
componentDidCatchrender() 出错时回退页面

setState

React 视图的改变来源于 state 的改变,类组件通过 setState 更新组件。

用法:

setState(partialState, callback)

partialState 可以为 null、对象、函数;callback 回调中可以拿到更新后的 state 值;

import React from 'react';

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

  changeNum = () => {
    // this.setState(null);
    this.setState(
      {
        num: this.state.num + 1
      },
      function () {
        console.log(this.state.num);
      }
    );
    this.setState({
      num: this.state.num + 1
    });
    this.setState((prevState, props) => {
      return {
        num: prevState.num + 1
      };
    });
  };

  render() {
    return (
      <div>
        <input type="text" value={this.state.num} readOnly />
        <button onClick={this.changeNum}>change</button>
      </div>
    );
  }
}

初始时页面显示 0 ,一次点击后变为 2。

到底是怎么更新的?

一次 setState 更新流程:

image.png

v16.13.1 版本

每次执行 setState ,内部执行 enqueueSetState ,创建一个 update ,把它放入当前 Fiber 更新队列中,执行 scheduleUpdateOnFiber调度更新。

那么一次触发多个 setState 是不是 render 多次呢?答案是否定的。React 会对多次 setState 进行批量更新

React 事件是合成事件,所有事件都会经过 dispatchEventForLegacyPluginEventSystem 函数,执行 batchedEventUpdates,通过

isBatchingEventUpdates 开关,控制批量更新。

image.png

代码流程:

一次点击事件 -> isBatchingEventUpdates 为 true -> changeNum,三次 setState 合并 -> render 执行 -> 更新 DOM -> callback 执行-> isBatchingEventUpdates 为 false

打破批量更新,同步执行

  • 如下改为,将 setState 包裹在 setTimeout 中
  changeNum = () => {
    setTimeout(() => {
      this.setState(
        {
          num: this.state.num + 1
        },
        function () {
          console.log(this.state.num);
        }
      );
      this.setState({
        num: this.state.num + 1
      });
      this.setState((prevState, props) => {
        return {
          num: prevState.num + 1
        };
      });
    });
  };

执行流程:

image.png

由于同步执行,页面显示最终效果是点击按钮,每次 +3 ,实际执行了 3 次 render

  • 绑定原生事件
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      num: 0
    };
  }

  handleNative = () => {
    this.setState({num: this.state.num + 1});
    this.setState({num: this.state.num + 1});
    this.setState({num: this.state.num + 1});
  }
  componentDidMount() {
    document.getElementById('native').addEventListener('click', this.handleNative)
  }
  render() {
    return (
      <div>
        <input type="text" value={this.state.num} readOnly />
        <button id="native">change</button>
      </div>
    );
  }
}

每点一次按钮,render 执行 3 次 ,页面显示结果 +3

ReactDOM.flushSync

以下代码点击后页面展示多少?

import React from 'react';
import ReactDOM from 'react-dom';

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

  changeNum = () => {
    this.setState({
      num: this.state.num + 1
    });
    this.setState({
      num: this.state.num + 1
    });
    this.setState((prevState, props) => {
      return {
        num: prevState.num + 1
      };
    });
    console.log('flushSync 前', this.state.num);
    // 触发 render 前面的 setState 会合并
    ReactDOM.flushSync(() => {
      this.setState((prevState, props) => {
        return {
          num: prevState.num + 1
        };
      });
    });
    console.log('flushSync 后', this.state.num);
  };
  render() {
    return (
      <div>
        <input type="text" value={this.state.num} readOnly />
        <button onClick={this.changeNum}>change</button>
      </div>
    );
  }
}

答案页面显示 3,控制台:flushSync 前 0,flushSync 后 3

flushSync setState 合并 -> render -> 更新

扩展一:flushSync 后加 setState,其他不变

  changeNum = () => {
    this.setState({
      num: this.state.num + 1
    });
    this.setState({
      num: this.state.num + 1
    });
    this.setState((prevState, props) => {
      return {
        num: prevState.num + 1
      };
    });
    console.log('flushSync 前', this.state.num);
    // 触发 render 前面的 setState 会合并
    ReactDOM.flushSync(() => {
      this.setState((prevState, props) => {
        return {
          num: prevState.num + 1
        };
      });
    });
    console.log('flushSync 后', this.state.num);
    this.setState({
      num: this.state.num + 1
    });
    console.log('flushSync setState 后', this.state.num);

  };

答案页面显示 4,控制台:flushSync 前 0,flushSync 后 3,flushSync setState 后 3

flushSync setState 合并 -> render -> 更新 -> setState -> render -> 更新

扩展二:加 setTimeout

changeNum = () => {
    this.setState({
      num: this.state.num + 1
    });
    this.setState({
      num: this.state.num + 1
    });
    this.setState((prevState, props) => {
      return {
        num: prevState.num + 1
      };
    });
    console.log('flushSync 前', this.state.num);
    // 触发 render 前面的 setState 会合并
    ReactDOM.flushSync(() => {
      this.setState((prevState, props) => {
        return {
          num: prevState.num + 1
        };
      });
    });
    console.log('flushSync 后', this.state.num);
    this.setState({
      num: this.state.num + 1
    });
    this.setState({
      num: this.state.num + 1
    });
    console.log('flushSync setState 后', this.state.num);
    setTimeout(() => {
      this.setState({
        num: this.state.num + 1
      });
      console.log('setTimeout 内', this.state.num);
    });
    console.log('setTimeout 外后', this.state.num);

  };

答案页面显示 5,控制台:flushSync 前 0,flushSync 后 3,flushSync setState 后 3,setTimeout 外后 3,setTimeout 内 5

flushSync setState 合并 -> render -> 更新 -> setState -> render -> 更新 -> setTimeout -> render -> 更新

总结:

v18 之前的版本(legacy 模式)

  • 处理 setState 事件为 React 事件,会批量更新合并 state,执行一次 render ,更新 DOM,执行 callback
  • 当为原生事件或 setStatesetTimeoutpromise 等异步任务包裹时,会同步显示结果,多次执行 render
  • ReactDOM.flushSync 同步条件下,其前面的 setState 和 内部的都会被合并(以内部的执行结果显示),执行一次 render。

v18 开启(concurrent 模式)

任何情况都会合并渲染,flushSync 包裹的调用后会立即渲染

props

组件间通信的重要方式。

展现形态有哪些?

如下代码:

function Hello() {
  return <div>hello function</div>;
}

class PropsKind extends React.Component {
  render() {
    const { msg, changeMsgFromChild, Component, renderProps } = this.props;
    const [slotChild1, slotChild2] = this.props.children;
    return (
      <div>
        <div>{msg}</div>
        <button onClick={changeMsgFromChild}>changeMsg</button>
        {<Component />}
        {renderProps()}
        {slotChild1}
        {slotChild2()}
      </div>
    );
  }
}
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      msg: 'hi'
    };
  }
  changeMsgFromChild = () => {
    this.setState({ msg: 'from child' });
  };
  render() {
    return (
      <div>
        <PropsKind
          msg={this.state.msg}
          changeMsgFromChild={this.changeMsgFromChild}
          Component={Hello}
          renderProps={() => <div>render props</div>}
        >
          <Hello />
          {() => <div>show somethig depend props children</div>}
        </PropsKind>
      </div>
    );
  }
}

组件打印的 props :

image.png

形态解释
简单变量msg={this.state.msg}传给组件内部渲染数据源
函数 changeMsgFromChild={this.changeMsgFromChild}子组件通知父组件的回调
组件 Component={Hello}传递组件
render props renderProps={() => <div>render props</div>} {() => <div>show somethig depend props children</div>}共享代码技术
组件的子元素 <PropsKind><Hello /></PropsKind>插槽的作用

具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。

官网例子

HOC 高阶组件

HOC 是复用组件逻辑的一个手段。

使用场景:

  • 作为属性代理

操作 props,可对 props 增加,删除。

function Hello(props){
  console.log(props);
  return <div>Hello</div>
}
const HOC = function (WrapperComponent) {
  return class extends React.Component {
   
    render() {
      const newProps = {
        ...this.props,
        name: 'HOC'
       }
      return <WrapperComponent {...newProps}/>;
    }
  };
};

const Hi = HOC(Hello);

class App extends React.Component {
  render(){
    return <Hi age="10" other="other"/>
  }
}

上面代码将组件 props 扩展了 name 属性。

  • 高阶组件获取 refs 引用是包装组件
class Hello extends React.Component {
  constructor(props) {
    super(props);
    console.log(props);
    const { getInstance } = props;
    getInstance(this);
  }
  render() {
    return <div>Hello</div>;
  }
}
const HOC = function (WrapperComponent) {
  return class extends React.Component {
    render() {
      const newProps = {
        ...this.props,
        name: 'HOC'
      };
      return <WrapperComponent {...newProps} />;
    }
  };
};

const Hi = HOC(Hello);

class App extends React.Component {
  componentDidMount() {
    // HOC 包裹组件
    console.log(this.node);
    // 组件实例
    console.log(this.childInstance);
  }
  render() {
    return <Hi age="10" other="other" ref={node => (this.node = node)} getInstance={instance => (this.childInstance = instance)} />;
  }
}

Hi 中 ref 拿到是包装组件,父组件中通过 getInstance 回调获取子组件实例,具体看打印信息:

image.png

  • 将受控组件状态提升到高阶组件中维护
const HOC = function (WrapperComponent) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = { name: '' };
    }

    onNameChange = e => {
      this.setState({
        name: e.target.value
      });
    };

    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange
        }
      };

      return <WrapperComponent {...newProps} {...this.props} />;
    }
  };
};

class Hello extends React.Component {
  render() {
    return (
      <div>
        {this.props.name.value}
        <input {...this.props.name} />
      </div>
    );
  }
}

const Hi = HOC(Hello);

class App extends React.Component {
  render() {
    return <Hi />;
  }
}
  • 改变组件容器布局样式
  • 反向继承(通过继承拿到组件的属性方法)
const HOC = function(WrapperComponent){
  return class extends WrapperComponent{
    if(this.props.visible){
      return super.render()
    } else {
      return <div>不展示</div>
    }
  }
}

总结:

高阶组件有两类:一类是属性代理;一类是反向继承;

使用注意事项

  • 不要在 render 中使用 HOC,否则会导致组件及子组件状态丢失,每次都创建新的高阶组件;
  • 手动拷贝静态方法,可用 hoist-non-react-statics 自动拷贝;
  • HOC 的 ref 不会传到子组件,可用 forwardRef 转发处理;
  • 不要在 HOC 内部修改原组件的原型属性,使用组合方式;

Hook

  • 为什么有?

代码复用:Hook 可以在无需修改组件结构的情况下复用状态逻辑;

代码管理:Hook 将组件中相互关联的部分拆分更小的函数,并发强制按生命周期划分;

class 组件较为笨重,是面向对象的思想,把一堆逻辑封装在内部,Hook 是一种函数式编程,符合 UI = f(data);

  • Hook 使用规则

只能在函数最外层调用 Hook。不要在循环,条件中使用。

只能在函数组件中调用。

API 使用

useState

[state, dispatch] = useState(initialData)

initData 为 state 初始值,可以是1、非函数类型;2、函数类型,返回值作为初始化值。

state 展示用。

dispatch 函数类型,更改 state 用。dispatch 参数可以为1、非函数,新值给 state;2、函数,入参为上一次的 state,返回值作为新的 state,更改视图(注意:不会自动合并更新对象)。另外会比较前后 state 是否更新,没更新,不会触发执行。

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

  const click = () => {
    setCount(count + 1);
    setCount(n => n + 1);
    // setCount(0);
    console.log(count);
    setTimeout(()=>{
      setCount(count + 1);
      console.log('timeout', count);
    })
  };
  console.log('render', count)

  return (
    <div>
      <p>{count}</p>
      <button onClick={click}>change</button>
    </div>
  );
}

页面展示1,控制台:0,render 2, render 1, timout 0

视图的更新是通过函数的重新执行。所以 click 内部打印的 count 是初始值。

前面两个 setCount 触发一次渲染,count 为 2(0+1,1+1);setTimout 内 setCount 触发一次渲染,count 为(0 + 1)。

useEffect

useEffect(callback, dep)

初始化执行,依赖项 dep 变化时会触发回调 callback

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

  useEffect(() =>{
    console.log('useEffect', count);
  }, [count])

  const click = () => {
    setCount(count + 1);
    setCount(n => n + 1);
    // setCount(0);
    // console.log(count);
    setTimeout(()=>{
      setCount(count + 1);
      // console.log('timeout', count);
    })
  };
  console.log('render', count)

  return (
    <div>
      <p>{count}</p>
      <button onClick={click}>change</button>
    </div>
  );
}
//页面由 0 ->1, render 0 useEffect 0,change -> render 2 useEffect 2, render 1 useEffect 1

用作数据交互,事件监听,返回值可以用来清理状态,会在浏览器完成 DOM 绘制后执行,不阻塞渲染

useLayoutEffect

在 DOM 绘制之前触发。阻塞浏览器的绘制。

useReducer

useState 的替代方案。const [state, dispatch] = useReducer(reducer, initialArg, init);

reducer 形如 (state, action)=>newState

initialArg 初始值

init 惰性初始化 state ,值 init(initialArg),将计算 state 逻辑提取到 reducer 外部

适用于state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

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

useContext

createContext 创建一个上下文,对外提供 Provider,消费者 Consumer。

useContext / Consumer 拿到最近 Provider 提供的 context。

const themes = {
  theme1: {
    color: 'red',
    background: 'green'
  },
  theme2: {
    color: 'green',
    background: 'red'
  }
};
const ThemeContext = createContext(null);

function ContextTestUse() {
  const theme = useContext(ThemeContext);

  return  <button style={{ background: theme.background, color: theme.color }}>useContext!</button>
}
function ContextTestConsumer() {
  return (
    <ThemeContext.Consumer>
      {theme => {
        return (
          <div>
            <button style={{ background: theme.background, color: theme.color }}>consumer!</button>
          </div>
        );
      }}
    </ThemeContext.Consumer>
  );
}
function App() {
  const [contextValue, setContextValue] = useState(themes.theme1);
  return (
    <div>
      <ThemeContext.Provider value={contextValue}>
        <ContextTestUse />
        <ContextTestConsumer />
      </ThemeContext.Provider>
      <button onClick={()=>setContextValue(themes.theme2)}>changeTheme</button>
    </div>
  );
}

useMemo useCallback

性能优化手段,会缓存值。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

const memoizedCallback = useCallback(fn, [dep]);

第一次渲染都执行,之后依赖发生变化时触发。useMemo 缓存变量,useCallback 缓存函数

/* 用 useMemo包裹的list可以限定当且仅当list改变的时候才更新此list,这样就可以避免selectList重新循环 */
 {useMemo(() => (
      <div>{
          selectList.map((i, v) => (
              <span
                  className={style.listSpan}
                  key={v} >
                  {i.patentName} 
              </span>
          ))}
      </div>
), [selectList])}

const Child = (props) => {
  /* 只有初始化的时候打印了 子组件更新 */
  console.log('子组件更新');
  useEffect(() => {
    props.getInfo('子组件');
  }, []);
  return <div>子组件</div>;
}
const DemoChildren = React.memo(Child);
// const DemoChildren = Child;
const App = ({ id }) => {
  const [number, setNumber] = useState(1);
  /* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) }
    经过处理赋值给 getInfo */
  const getInfo = useCallback(
    sonName => {
      console.log(sonName);
    },
    [id]
  );
  return (
    <div>
      {/* 点击按钮触发父组件更新 ,但是子组件没有更新 */}
      {number}
      <button onClick={() => setNumber(number + 1)}>增加</button>
      <DemoChildren getInfo={getInfo} />
    </div>
  );
};

useRef

可用来获取 DOM 元素,组件实例。

返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的 ref 对象都是同一个。

function App() {
  const inputEl = useRef(null);
  const curFocus = () => {
    // input DOM
    console.log(inputEl.current);
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={curFocus}>Focus the input</button>
    </>
  );
}

useImpreativeHandle

配合 forwardRef ,自定义返回 ref 对象给父组件使用。

class Hello extends React.Component {
  render() {
    return <div>hello</div>;
  }
}

function Hi(props, ref) {
  const inputRef = useRef();
  // ref 传过来的 回调返回新的 ref 给父组件
  useImperativeHandle(ref, () => {
    return {
      focus: () => {
        inputRef.current.focus();
      }
    };
  });
  return <div ref={inputRef}>Hi</div>;
}
// 转发 ref
const Hin = forwardRef(Hi);

function App() {
  const refH = useRef(null);
  const refF = useRef(null);
  useEffect(() => {
    console.log(refH);
    console.log(refF);
  });
  return (
    <div>
      <Hello ref={refH} />
      <Hin ref={refF} />
    </div>
  );
}

参考

官方文档

React深度剖析+实战

React 进阶实践指南