如何使用 React Hooks 重构类组件?(上)

简介: 如何使用 React Hooks 重构类组件?

最初,在 React 中可以使用 createClass 来创建组件,后来被类组件所取代。在 React 16.8版本中,新增的 Hooks 功能彻底改变了我们编写React程序的方式,因为使用 Hooks 可以编写更简洁、更清晰的代码,并为创建可重用的有状态逻辑提供了更好的模式。

许多公司和开发人员都放弃了类组件转而使用 Hooks。而许多旧的的React 项目仍然在使用类组件。更重要的是,在类组件中有 Error Boundaries,而函数组件中是无法使用 Error Boundaries 的。

本文就来通过一些常见示例看看如何使用 React Hooks 来重构类组件。


1. 管理和更新组件状态


状态管理是几乎所有 React 应用中最重要的部分,React 基于 state 和 props 渲染组件。每当它们发生变化时,组件就会重新渲染,并且 DOM 也会相应地更新。下面来看一个计数器的例子,它包含一个计数状态以及两个更新它的地方:

import { Component } from "react";
class ManagingStateClass extends Component {
  state = {
    counter: 0,
  };
  increment = () => {
    this.setState(prevState => {
      return {
        counter: prevState.counter + 1,
      };
    });
  };
  decrement = () => {
    this.setState(prevState => {
      return {
        counter: prevState.counter - 1,
      };
    });
  };
  render() {
    return (
      <div>
        <div>Count: {this.state.counter}</div>
        <div>
          <button onClick={this.increment}>Increment</button>
          <button onClick={this.decrement}>Decrement</button>
        </div>
      </div>
    );
  }
}
export default ManagingStateClass;

下面来使用 Hooks 实现这个计数器组件:


import { useState } from "react";
const ManagingStateHooks = () => {
  const [counter, setCounter] = useState(0);
  const increment = () => setCounter(counter => counter + 1);
  const decrement = () => setCounter(counter => counter - 1);
  return (
    <div>
      <div>Count: {counter}</div>
      <div>
        <button onClick={increment}>Increment</button>
        <button onClick={decrement}>Decrement</button>
      </div>
    </div>
  );
};
export default ManagingStateHooks;

该组件是一个返回 JSX 的函数,使用 useState hook来管理计算器的状态。它返回一个包含两个值的数组:第一个值为状态,第二个值为更新函数。并且使用 setCounter 来更新程序的incrementdecrement函数。


2. 状态更新后的操作

在某些情况下,我们可能需要在状态更新时执行某些操作。在类组件中,我们通常会在componentDidUpdate 生命周期中实现该操作。

import { Component } from "react";
class StateChangesClass extends Component {
  state = {
    counter: 0,
  };
  componentDidUpdate(prevProps, prevState) {
    localStorage.setItem("counter", this.state.counter);
  }
  increment = () => {
    this.setState(prevState => {
      return {
        counter: prevState.counter + 1,
      };
    });
  };
  decrement = () => {
    this.setState(prevState => {
      return {
        counter: prevState.counter - 1,
      };
    });
  };
  render() {
    return (
      <div>
        <div>Count: {this.state.counter}</div>
        <div>
          <button onClick={this.increment}>Increment</button>
          <button onClick={this.decrement}>Decrement</button>
        </div>
      </div>
    );
  }
}
export default StateChangesClass;

当状态发生变化时,我们将新的计数器值保存在 localStorage 中。在函数组件中,我们可以通过使用 useEffect hook 来实现相同的功能。

import { useState, useEffect } from "react";
const StateChangesHooks = () => {
  const [counter, setCounter] = useState(0);
  const increment = () => setCounter(counter => counter + 1);
  const decrement = () => setCounter(counter => counter - 1);
  useEffect(() => {
    localStorage.setItem("counter", counter);
  }, [counter]);
  return (
    <div>
      <div>Count: {counter}</div>
      <div>
        <button onClick={increment}>Increment</button>
        <button onClick={decrement}>Decrement</button>
      </div>
    </div>
  );
};
export default StateChangesHooks;

这个 useEffect hook 有两个参数,第一个参数是回调函数,第二个参数是依赖数组。在组件挂载时,这个 hook 至少会执行一次。然后,仅在依赖数组内的任何值发生变化时都会触发第一个参数传入的回调函数。如果依赖数组为空,则回调函数只会执行一次。在上面的例子中,每当 counter 发生变化时,都会触发将 counter 保存在 localStorage 中的回调函数。


3. 获取数据

在类组件中,通过会在componentDidMount生命周期中初始化一个 API 请求来获取数据。下面来看一个获取并显示帖子列表的组件:


import { Component } from "react";
class FetchingDataClass extends Component {
  state = {
    posts: [],
  };
  componentDidMount() {
    this.fetchPosts();
  }
  fetchPosts = async () => {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    const data = await response.json();
    this.setState({
      posts: data.slice(0, 10),
    });
  };
  render() {
    return (
      <div>
        {this.state.posts.map(post => {
          return <div key={post.id}>{post.title}</div>;
        })}
      </div>
    );
  }
}
export default FetchingDataClass

有了 hooks,就可以使用useEffect来实现上述功能。它会在第一次挂载之后执行一次,然后在任何依赖发生变化时再次触发。useEffect 允许我们传入一个空依赖数组作为第二个参数来确保只执行一次effect的回调函数。

import { useState, useEffect } from "react";
const FetchingDataHooks = () => {
  const [posts, setPosts] = useState([]);
  const fetchPosts = async () => {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    const data = await response.json();
    setPosts(data.slice(0, 10));
  };
  useEffect(() => {
    fetchPosts();
  }, []);
  return (
    <div>
      {posts.map(post => {
         return <div key={post.id}>{post.title}</div>;
      })}
    </div>
  );
};
export default FetchingDataHooks;


4. 卸载组件时清理副作用


在卸载组件时清理副作用是非常重要的,否则可能会导致内存泄露。例如,在一个组件中,我们想要监听一个事件,比如resize或者scroll,并根据窗口大小获滚动的位置来做一些事情。下面来看一个类组件的例子,它会监听 resize 事件,然后更新浏览器窗口的宽度和高度的状态。事件监听器在 componentWillUnmount 生命周期中被移除。


import { Component } from "react";
class CleanupClass extends Component {
  state = {
    width: window.innerWidth,
    height: window.innerHeight,
  };
  componentDidMount() {
    window.addEventListener("resize", this.updateWindowSize, {
      passive: true,
    });
  }
  componentWillUnmount() {
    window.removeEventListener("resize", this.updateWindowSize, {
      passive: true,
    });
  }
  updateWindowSize = () => {
    this.setState({
      width: window.innerWidth,
      height: window.innerHeight,
    });
  };
  render() {
    return (
      <div>
        Window: {this.state.width} x {this.state.height}
      </div>
    );
  }
}
export default CleanupClass;

useEffect 中,我们可以在回调函数中返回一个函数来执行清理操作,卸载组件时会调用此函数。下面,首先来定义一个 updateWindowSize 函数,然后在 useEffect 中添加 resize 事件监听器。 接下来返回一个匿名箭头函数,它将用来移除监听器。


import { useState, useEffect } from "react";
const CleanupHooks = () => {
  const [width, setWidth] = useState(window.innerWidth);
  const [height, setHeight] = useState(window.innerHeight);
  useEffect(() => {
    const updateWindowSize = () => {
      setWidth(window.innerWidth);
      setHeight(window.innerHeight);
    };
    window.addEventListener("resize", updateWindowSize, {
      passive: true,
    });
    return () => {
      window.removeEventListener("resize", this.updateWindowSize, {
        passive: true,
      });
    };
  }, []);
  return (
      <div>
        Window: {this.state.width} x {this.state.height}
      </div>
  );
};
export default CleanupHooks;


如何使用 React Hooks 重构类组件?(下)https://developer.aliyun.com/article/1411376

相关文章
|
8月前
|
前端开发
轻松掌握 React Hooks:简化状态与副作用管理
轻松掌握 React Hooks:简化状态与副作用管理
257 80
|
8月前
|
前端开发
React Hooks数据获取:避免内存泄漏的实战指南
React Hooks数据获取:避免内存泄漏的实战指南
|
4月前
|
缓存 前端开发 JavaScript
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
🌟蒋星熠Jaxonic,前端探索者。专注React Hooks深度实践,从原理到实战,分享状态管理、性能优化与自定义Hook精髓。助力开发者掌握函数组件的无限可能,共赴技术星辰大海!
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
|
9月前
|
缓存 前端开发 数据安全/隐私保护
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
319 68
|
9月前
|
缓存 前端开发 Java
在 React 中,组合组件和高阶组件在性能方面有何区别?
在 React 中,组合组件和高阶组件在性能方面有何区别?
292 67
|
9月前
|
前端开发 JavaScript 安全
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
363 62
|
11月前
|
前端开发
React 中高阶组件的原理是什么?
React 中高阶组件的原理是什么?
258 57
|
11月前
|
前端开发 开发者
除了函数组件和类组件,React 还有其他创建组件的方式吗?
除了函数组件和类组件,React 还有其他创建组件的方式吗?
207 57
|
11月前
|
前端开发
如何在React Router中定义404错误页面组件?
如何在React Router中定义404错误页面组件?
296 57
|
11月前
|
前端开发
在 React 中使用高阶组件时,如何避免命名冲突?
在 React 中使用高阶组件时,如何避免命名冲突?
268 56