Skip to content

react 基础-常用 Hook 和 API

Hook —— 在组件中使用不同的 React 特性。 内置 Hook 都是在函数顶层调用

useState

作用:保存组件数据并触发更新界面 用法:const [state,setState] = React.useState(xx); setState(newState) 或者 setState(state=>newState)

useRef

作用:保存组件数据、指向真实 DOM 或者组件实例,更改不会触发更新界面 用法:const myRef = React.useRef(xx);myRef.current 可读写

useEffect

作用:组件渲染完成后执行交互,如浏览器 API、以及网络请求数据等 用法:

js
React.useEffect(() => {
  //在此执行副作用操作
  // 每次渲染后都会执行此处的代码
  //...
  return () => {
    //返回的函数是组件卸载的时候执行的
    console.log("unmount--will call");
  };
}); //没有传第二个参数,每次渲染都会执行第一个函数的代码

React.useEffect(() => {
  //在此执行副作用操作
  return () => {
    //返回的函数是组件卸载的时候执行的,但如果监控了某个状态值,每次更新也会调用
    console.log("unmount--will call");
  };
}, [xx]);
//第二个参数,如果指定的是[],回调函数只有在第一次 render 的时候执行
//第二个参数有值(依赖),就表示这里面的值变化了之后,副作用也会执行,这是 react 专门设置的,为了更新的时候先清除数据再渲染
//跟 useState 一样,useEffect 也可以调用多次,可以在不同的 useEffect 里面实现不同的副作用,达到关注点分离

编写 Effect 需要遵循以下三个规则:

  1. 声明 Effect。默认情况下,Effect 会在每次渲染后都会执行。
  2. 指定 Effect 依赖。大多数 Effect 应该按需执行,而不是在每次渲染后都执行。
  3. 必要时添加清理(cleanup)函数。

forwardRef(API)

作用:将函数组件内部的组件或者是 DOM 节点暴露给父组件。

  1. 一个函数组件要传 ref,必须将组件定义包装在 forwardRef() 中。
  2. 默认情况下,每个组件的 DOM 节点都是私有的。然而,有时候将 DOM 节点公开给父组件是很有用的,比如允许对它进行聚焦。 用法:
js
const MyInput = React.forwardRef(function MyInput(props, ref) {
  return (
    <label>
      {props.label}
      <input ref={ref} />
    </label>
  );
});

useImperativeHandle

作用:forwardRef 会暴露真实的 DOM 节点,借助 useImperativeHandle 可以实现自定义暴露,只暴露提供自定义的方法集 用法: useImperativeHandle(ref, createHandle, dependencies?) 在组件顶层通过调用 useImperativeHandle 来自定义 ref 暴露出来的句柄:

js
const MyInput = React.forwardRef(function MyInput(props, ref) {
  React.useImperativeHandle(
    ref,
    () => {
      return {
        // ... 你的方法 ...
      };
    },
    []
  );
});

useCallback

对于函数组件里面的回调函数,在组件重新渲染时,都会去重新创建执行 使用 useCallback 可以记忆函数,不会造成每次都重新创建,只有在对应的依赖更新时才会调用,起到缓存作用。 结合 memo API 的使用,可以允许你的组件在 props 没有改变的情况下跳过重新渲染。

js
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);
// 不会执行第一个参数函数,只是直接返回,来达到存储函数的目的
// 常用来记忆事件函数,生成记忆后的时间函数并传递给子组件使用

memo(API)

memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。 memo(Component, arePropsEqual?)

js
function SomeComponent(){
  xxxx
}
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
const MemoizedComponent = memo(SomeComponent, (prevProps, nextProps) => {
  // 返回true表示相同不重新渲染
  return (
    prevProps.xxx === nextProps.xxx
  );
})

useMemo

使用 useMemo 在每次重新渲染的时候能够缓存计算的结果。

js
const cachedValue = useMemo(() => {
  //经过一系列计算后的结果
}, dependencies);

useCallback(fn, deps) 相当于 useMemo(() => fn, deps) 唯一区别:useMemo 会执行第一个参数函数,并返回函数执行结果

类组件简单介绍

类组件写法

js
/* 1.定义类组件 */
class MyCompont extends React.Component {
  render() {
    const VDOM = <h1>Hello React</h1>;
    console.log("VDOM", VDOM);
    return VDOM;
  }
}
const root = ReactDOM.createRoot(document.getElementById("root"));
/* 2.将类组件渲染到页面上 */
root.render(<MyCompont />);

state 和事件处理

  1. 定义 state 一般直接简写为 state=
  2. 组件中 render 方法中的 this 是实例对象本身
  3. 因为组件中的方法都是给回调使用,而非实例对象使用的,所以 this 是 undefined,解决方法有: 1).强制绑定 this:通过 bind 来强制绑定 this 并返回新函数 2).自定义函数用函数表达式+箭头函数
  4. 状态数据不能直接修改,要使用 this.setState({})来修改,且传入的参数是合并的操作,而不是替换,但只限于合并对象的一级属性,再往下应该就是整个替换

完整写法

js
class Weather extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isHot: true, wind: "热风" };
    // 解决changeHot的this指向问题
    this.changeHot = this.changeHot.bind(this);
  }
  render() {
    console.log("render中的this", this);
    //添加点击事件
    return (
      <h2 onClick={this.changeHot}>
        今天天气很{this.state.isHot ? "炎热" : "凉爽"},{this.state.wind}
      </h2>
    );
    //如果这样子写,就是实例调用,不需要再改变changeHot的this了
    // return <h2 onClick={()=>{this.changeHot()}}>今天天气很{this.state.isHot?"炎热":"凉爽"},{this.state.wind}</h2>;
  }
  changeHot() {
    console.log("changeHot--this", this);
    //changeHot是放在哪里的?--Weather的原型对象上,供实例使用
    //如果通过给h2点击onclick事件调用,那就是回调函数调用,是直接window调用,而非实例对应
    //类中的方法都是开启了严格模式,this是undefined
    // 严重注意,状态的修改必须通过setState({})修改,且传入的参数是合并的操作,而不是替换
    this.setState({ isHot: !this.state.isHot });
    // console.log("changeHot中的this",this);
  }
}

简写

js
class Weather extends React.Component {
  // 初始化状态
  state = { isHot: true, wind: "热风" };
  render() {
    console.log("render中的this", this);
    //添加点击事件
    return (
      <h2 onClick={this.changeHot}>
        今天天气很{this.state.isHot ? "炎热" : "凉爽"},{this.state.wind}
      </h2>
    );
  }
  // 自定义方法,要用赋值语句+箭头函数的形式
  changeHot = () => {
    this.setState({ isHot: !this.state.isHot });
  };
}

ref

组件内的标签可以通过 ref 来标识自己 如果是在组件内的虚拟 DOM 加上 ref,ref 获取到的就是真实的 DOM 如果是在组件加上 ref,获取到的就是组件实例对象,可以通过这个实例对象调用组件内的方法

ref 有三种使用方式

1.字符串的ref,直接在标签内ref写上字符串,会自动保存在refs中,通过字符串名字引用。(这种方式以后可能会被废除)
     <input ref="input1" type="text"/>
     this.refs.input1获取
2.回调方式的ref
    内联函数回调(在数据更新时会触发两次回调,一次传null是为了清空数据,一次才是真实的dom)
    <input ref={(c) => {this.input1 = c}} type="text"/>
    this.input1获取
    类绑定函数回调(还要自定义函数,写法复杂)
    <input ref={this.saveNode} type="text"/>
3.React.createRef()创建
    myRef = React.createRef();//这个放在实例自身上
    <input ref={this.myRef} type="text"/>
    myRef.current获取

生命周期

组件对象从创建到死亡,会经历特定阶段;React 组件对象包含一系列钩子函数(生命周期回调函数),在特定的时刻调用;在特定的生命周期回调函数中做特定的工作 生命周期分为分为三个阶段:

  1. 初始化阶段:由 ReactDOM.render()触发--初次渲染 挂载的流程: constructor ==> render ==> componentDidMount componentDidMount 比较常用,一般在这个钩子函数中做一些初始化的事,比如说设置定时器、发送网络请求、订阅消息
  2. 更新阶段:组件内部 this.setState({})或者父组件重新 render 触发 更新的流程 setState ==> shouldComponentUpdate ==> componentWillUpdate ==> render ==> componentDidUpdate shouldComponentUpdate 的返回值是 boolean 值,只有为 true 才会继续往下走 不写 shouldComponentUpdate 钩子的话,默认返回是 true 强制更新流程(调用 this.forceUpdate())触发 forceUpdate ==> componentWillUpdate ==> render ==> componentDidUpdate
  3. 卸载组件: componentWillUnmount 比较常用,一般在这个钩子函数中做一些收尾的事情,比如说取消定时器、取消订阅消息
js
class Count extends React.Component {
  constructor(props) {
    console.log("Count--constructor");
    super(props);
    this.state = { sum: 0 };
  }
  // 组件即将挂载的钩子
  componentWillMount() {
    console.log("Count--componentWillMount");
  }

  //挂载完成后的钩子
  componentDidMount() {
    console.log("Count--componentDidMount");
  }

  //控制组件更新的阀门
  shouldComponentUpdate() {
    console.log("Count--shouldComponentUpdate");
    return true;
    // return false;//返回false
  }

  // 组件将要更新的钩子
  componentWillUpdate() {
    console.log("Count--componentWillUpdate");
  }
  // 组件更新完成的钩子
  componentDidUpdate() {
    console.log("Count--componentDidUpdate");
  }

  //组件即将卸载的钩子
  componentWillUnmount() {
    console.log("Count--componentWillUnmount");
    clearInterval(this.timer);
  }
  //加1的回调
  add = () => {
    console.log("Count--add");
    this.setState({ sum: this.state.sum + 1 });
  };
  //强制更新按钮的回调
  force = (event) => {
    console.log("Count--force");
    // console.log(event,event.target);
    this.forceUpdate();
  };

  //卸载按钮的回调
  unmount = () => {
    console.log("Count--death");
    ReactDOM.unmountComponentAtNode(document.getElementById("test"));
  };

  // 初始化渲染、数据更新
  render() {
    console.log("Count--render");
    return (
      <div>
        <h2>当前求和为:{this.state.sum}</h2>
        <button onClick={this.add}>点我+1</button>
        <button onClick={this.unmount}>卸载组件</button>
        <button onClick={this.force}>不setState,强制更新</button>
      </div>
    );
  }
}

作业

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>作业</title>
    <style>
      @charset "utf-8";
      /* 样式重置 */
      h1,
      h2,
      h3,
      h4,
      h5,
      h6,
      header,
      hgroup,
      hr,
      input,
      li,
      ol,
      p,
      pre,
      td,
      textarea,
      th,
      ul {
        margin: 0;
        padding: 0;
      }
      ul,
      oll,
      li {
        list-style: none;
      }
      a {
        text-decoration: none;
        color: #333;
      }
      i,
      em {
        font-style: normal;
      }
      input,
      textarea,
      button,
      select,
      a {
        outline: none;
        border: none;
      }
      table {
        border-collapse: collapse;
        border-spacing: 0;
      }

      .todo-container {
        position: static;
      }
      .todo-wrap {
        width: 520px;
        margin: 20px auto;
        border: 1px solid #999;
        border-radius: 5px;
      }
      .todo-main {
        margin: 10px;
        border: 1px solid #999;
        border-bottom-width: 0;
        width: 500px;
      }
      .btn {
        padding: 5px 12px;
        text-align: center;
        font-size: 16px;
        border-radius: 4px;
      }
    </style>
    <script>
      function onkeyDown() {
        const keycode = window.event.keyCode;
        if (keycode === 13) {
          alert("按下了回车按键 ");
        }
      }
    </script>
  </head>
  <body>
    <div class="todo-wrap">
      第15讲作业说明:<br />
      1.提供一个Clock的函数组件,实现一个计时器的功能,对外提供的三个接口:start、stop、clear,分别对应开始计时、停止计时、和复位。<br />
      2.Clock组件的界面:一个启动或者是停止的按钮,启动的时候显示停止,停止的时候显示启动,一个复位按钮,一个显示点击启动计时后经历过的时间。<br />
      3.父组件上有一个计算器,默认值是1,一个加按钮点击加1,一个减按钮点击减1;下面是两个Clock子组件(计时器1、计时器2)<br />
      4.当计算器值为1,5,9,13……时计算器1自动启动,为2,6,10,14……时计时器1自动停止,为3,7,11,15……时计时器2自动启动,为4,8,12,16……时计时器2自动停止<br />
    </div>
    <div class="todo-container">
      <div class="todo-wrap">
        <h3>父组件</h3>
        <div style="text-align: center">
          <button class="btn">-</button>
          <span> 当前计数:1 </span>
          <button class="btn">+</button>
        </div>
        <div style="background: #999; height: 1px"></div>
        <div style="display: flex; flex-direction: row">
          <div style="flex: 1; padding: 10px">
            <h3>计时器1:</h3>
            <div style="text-align: center">00:00:00</div>
            <div
              style="
                margin: 0 10px;
                display: flex;
                flex-direction: row;
                justify-content: space-between;
              "
            >
              <button>启动/停止</button>
              <button>复位</button>
            </div>
          </div>
          <div style="background: #999; width: 1px"></div>
          <div style="flex: 1; padding: 10px">
            <h3>计时器1:</h3>
            <div style="text-align: center">00:00:00</div>
            <div
              style="
                margin: 0 10px;
                display: flex;
                flex-direction: row;
                justify-content: space-between;
              "
            >
              <button>启动/停止</button>
              <button>复位</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>