React中 类组件 与 函数组件 的区别

4 篇文章 0 订阅

1. 类组件

在React中,类组件就是基于ES6语法,通过继承 React.component 得到的组件。

class Demo extends React.Component {
  // 初始化类组件的 state
  state = {
    text: "111"
  };
  // 编写生命周期方法 didMount
  componentDidMount() {
    // 省略业务逻辑
  }
  // 编写自定义的实例方法
  changeText = (newText) => {
    // 更新 state
    this.setState({
      text: newText
    });
  };
  // 编写生命周期方法 render
  render() {
    return (
      <div className="demoClass">
        <p>{this.state.text}</p>
        <button onClick={() => this.changeText(222)}>点我修改</button>
      </div>
    );
  }
}

2. 函数组件

在函数组件也称为无状态组件,顾名思义就是以函数形态存在的 React 组件。

hooks出现之前,react 中的函数组件通常只考虑负责UI的渲染,没有自身的状态,没有业务逻辑代码,是一个纯函数

下面这个函数组件就是一个纯函数,它的输出只由参数props决定,不受其他任何因素影响。

function DemoFunction(props) {
  const { text } = props
  return (
    <div className="demoFunction">
      <p>{`function 组件所接收到的来自外界的文本内容是:[${text}]`}</p>
    </div>
  );
}

但是这种函数组件一旦我们需要给组件加状态,那就只能将组件重写为类组件,因为函数组件没有实例,没有生命周期
所以我们说在 hook 之前的 函数组件和类组件最大的区别就是 状态的有无。

Hook

React Hooks 是从 React 16.8 版本推出的新特性,目的是解决 React 的状态共享以及组件生命周期管理混乱的问题。 React Hooks 的出现标志着,React 不会再存在无状态组件的情况,React 将只有类组件和函数组件的概念。

hooks为函数组件提供了状态,也支持在函数组件中进行数据获取、订阅事件解绑事件等等。

下面先介绍几个最基本的hook作为基础知识。

useState

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

通过 useState 为组件提供状态。useState 的参数是 state 的初始值,他只有在组件第一次渲染的时候会生效,他的返回值是一个数组,第一个是 state,第二个是设置state的函数。

useRef

const ref = useRef(initialValue)
  • 用于在函数组件之间保存可变的引用,类似于类组件中的 ref。改变 ref 不会触发重新渲染。

  • 使用 ref 操作 DOM

    import { useRef } from 'react';
    export default function Form() {
      const inputRef = useRef(null);
      function handleClick() {
        inputRef.current.focus();
      }
      return (
        <>
          <input ref={inputRef} />
          <MyInput ref={inputRef} />;// 无法获取自定义组件的 ref 
          <button onClick={handleClick}>
            聚焦输入框
          </button>
        </>
      );
    }
    
  • 无法获取自定义组件的 ref
    默认情况下,自定义组件不会暴露它们内部 DOM 节点的 ref。
    像这样将自定义子组件包装在 forwardRef 里,父级组件就可以得到它的 ref。
    假设你不想暴露出整个 <input> DOM 节点,可以采用useImperativeHandle向父组件暴露一个自定义的 ref

    import { forwardRef } from 'react';
    const MyInput = forwardRef(({ value, onChange }, ref) => {
     useImperativeHandle(ref, () => ({
        // 只暴露 focus、scrollIntoView,没有别的
    	    focus() {
    	      realInputRef.current.focus();
    	    },
    	    scrollIntoView() {
              inputRef.current.scrollIntoView();
            },
      }),[]);
      return (
        <input
          value={value}
          onChange={onChange}
          ref={ref}
        />
      );
    });
    export default MyInput;
    

useEffect

//1. 不设置依赖项数组,那么在组件挂载时和每次渲染时,都会执行一次副作用
useEffect(() => {
    console.log("useEffect副作用执行");
    //用setTimeout模拟ajax请求
    setTimeout(() => {
      setList(result);
    }, 3000);
  });
//2. 设置依赖项为空数组,那么只有在组件挂载和卸载时执行副作用
useEffect(() => {
    console.log("useEffect副作用执行");
    setTimeout(() => {
      setList(result);
    }, 3000);
    return ()=>{
		// 组件卸载前的操作
	}
  },[]);// 空的依赖数组确保只在组件挂载和卸载时执行一次
//3. 如果设置依赖项且不为空,那么在组件挂载时和数组中的依赖项发生变化时,副作用就会重新执行。
 useEffect(() => {
    console.log("useEffect副作用执行");
    setTimeout(() => {
      setList(result);
    }, 3000);
    return ()=>{
		//在下一次 useEffect 执行前执行
	}
  }, [list]);

useEffect用于处理组件的副作用

  • 第一个参数是一个回调函数,在里面进行业务逻辑代码的书写(副作用操作)
    • 通常在副作用中进行ajax请求,事件的绑定与解绑,设置定时器与清除等等。
  • 第二个参数是依赖项数组,指定副作用函数的触发条件。
    • 如果不设置第二个参数,那么当该组件挂载和组件每渲染一次,副作用就会执行一次
    • 如果数组中的依赖设置为,那么只会在组件挂载和卸载时执行一次副作用。在 useEffect 的回调函数中,可以执行组件挂载时的操作,并在return返回的清理函数中执行组件卸载前的操作(例如:清除定时器,取消订阅)。
    • 如果数组中存在依赖项,当组件挂载时和依赖项数组中的依赖发生变化,那么该副作用就会重新执行 ,在 useEffect 的回调函数中,return返回的清理函数在下一次 useEffect 执行前执行。

useCallback

用于缓存函数,以避免无效的函数重新创建。 第一个参数为要缓存的函数,第二个参数为依赖项数组,如果依赖发生了变化,那么就会生成一个新的函数;否则当组件重新渲染时,不会重新定义这个函数,而是会取缓存。

useMemo

用于缓存计算结果,以避免无效的计算重复执行。 第一个参数为要缓存的函数(注意实际被缓存的是函数被执行过后的值),第二个参数为依赖项数组,如果依赖发生了变化,那么就会重新执行这个函数,得到新的返回值;否则当组件重新渲染时,不会重新执行这个函数,而是直接取被缓存的该函数的返回值。

useCallbackuseMemo 都是在函数式组件中用作优化性能的。

默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件。组件中的函数就会重新被创建。因为在 JavaScript 中,function () {} 或者 () => {} 总是会生成不同的函数。

  • useCallback
    • 避免在内部编写额外的嵌套函数
    • 避免组件的重新渲染

比如说在父组件中接收的props有{ A,B,C },函数handleSubmit()依赖于A和B,将这个函数作为子组件的props),如果C发生变化,会导致重新创建一个函数handleSumit(),也就导致子组件的props是不同,从而引起子组件重新渲染,但其实这是不必要,这时就可以采用useCallback,将函数handleSubmit传递给useCallback,并设置依赖项([A,B]),保证只有依赖项(A,B)发生变化时,才会重新生成handleSubmit函数,子组件才会重新渲染,从而避免了组件内部重新创建函数,避免引起不必要的渲染。

  • useMemo
    • 避免无效的重新计算
    • 跳过组件的重新渲染

同理,比如说父组件中接收的props有{A,B,C},有一个对象count是依赖于A和B计算得出的,并将这个对象count作为子组件的props,正常情况当C发生变化,会导致重新创建对象count,导致传递给子组件的props不同,从而引起子组件的重新渲染,但这是不必要的重复渲染,这时就可以采用useMemo,将定义对象count的计算函数用useMemo包裹,并设置依赖项[A,B],这样就可以保证只有在依赖项发生变化时,才会重新执行计算函数生成新的count对象,子组件才会重新渲染,从而避免了组件内部重新进行无效计算,避免引起不必要的渲染。

useContext

用于跨组件通信,以便共享数据和功能。它接收一个上下文对象(通过 React.createContext 创建)并返回当前上下文的值。这样,函数组件就可以消费上下文中的数据,而不必通过一层层的属性传递。

import { createContext, useContext } from 'react';
const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}

function Form() {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ children }) {
  const theme = useContext(ThemeContext);
  const className = 'button-' + theme;
  return (
    <button className={className}>
      {children}
    </button>
  );
}

useContext() 总是在调用它的组件 上面 寻找最近的 provider。它向上搜索, 不考虑 调用 useContext() 的组件中的 provider。

3. 函数组件与类组件的区别

3.1 表面差异

  • 类组件有生命周期,函数组件没有
  • 类组件需要继承 Class,函数组件不需要
  • 类组件可以获取实例化的 this,并且基于 this 做各种操作,函数组件不行
  • 类组件内部可以定义并维护 state, 函数组件为无状态组件(可以通过hooks实现)

函数组件相比较类组件,优点是更轻量与灵活,便于逻辑的拆分复用。

3.2 最大不同

类组件捕获最新的值,函数式组件捕获了渲染时所使用的值 , 这是两类组件最大的不同。

在React中,函数组件 每次渲染组件中的变量和事件处理函数就会重新被创建。每次渲染React都会提供给这次渲染的state快照,也就是说state 的值始终“固定”在一次渲染的各个事件处理函数内部。所以,过去创建的事件处理函数拥有的是创建它们的那次渲染中的 state 值。
而在类组件中,事件处理函数是通过this获取最新的state值。

下面这个发送信息的例子是官网上的例子,我们分别在类组件和函数组件中测试一下看看有什么不同:
在这里插入图片描述
类组件中:

class Form1 extends React.Component{
    state={
      to:'Alice',
      message:'Hello'
    }
    handleSubmit=(e)=>{
      e.preventDefault();
      setTimeout(() => {
        alert(`You said ${this.state.message} to ${this.state.to}`);
      }, 5000);
    }

    render(){
      return  <form onSubmit={this.handleSubmit}>
        <label>
          To:{' '}
          <select
            value={this.state.to}
            onChange={e => this.setState({to:e.target.value})}>
            <option value="Alice">Alice</option>
            <option value="Bob">Bob</option>
          </select>
        </label>
        <textarea
          placeholder="Message"
          value={this.state.message}
          onChange={e => this.setState({message:e.target.value})}
        />
        <button type="submit">Send</button>
    </form>
    }
}

函数组件中:

import { useState } from 'react';
export default function Form() {
  const [to, setTo] = useState('Alice');
  const [message, setMessage] = useState('Hello');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`You said ${message} to ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        To:{' '}
        <select
          value={to}
          onChange={e => setTo(e.target.value)}>
          <option value="Alice">Alice</option>
          <option value="Bob">Bob</option>
        </select>
      </label>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}

仔细对比,会发现:

  • 类组件中,当点击【Send】按钮后,如果在5秒内修改收件人(下拉列表中的选项,由Alice—>Bob),那么最终页面alert弹窗内容中收件人是最新修改的名字Bob。
  • 函数组件中,重复以上操作,5秒后,最终页面alert弹窗内容中收件人是点击【Send】按钮时的名字Alice,不是最新修改的名字Bob。

此案例中可以说明,在函数组件中,一个 state 变量的值永远不会在一次渲染的内部发生变化即使其事件处理函数的代码是异步的。React会为每次特定的那一次渲染提供一张 state 快照。组件会在其 JSX 中返回一张包含一整套新的 props 和事件处理函数的 UI 快照 ,其中所有的值都是 根据那一次渲染中 state 的值 被计算出来的!

原因

  • 类组件: 可以直接获取组件最新的值

    原因:类组件捕获最新的值(永远保持一致)当实例的state或props属性发生修改时,类组件直接使用this(组件的实例),所以可以直接获取组件最新的值

  • 函数组件: 函数式组件捕获了渲染时所用的值

    原因:函数式组件捕获了渲染所使用的值。在函数组件中,之前的state或props值,已经因为javaScript闭包的特性,保存在内存之中,无法从外部进行修改(维护多个人的状态)。 所以在定时器执行callback时,打印的还是旧值。

下面是在javaScript闭包中的测试

<body>
	<button onclick="openClick()">开始</button>  
    <button onclick="handleClick()">改变a的值</button>    
</body>
<script>
function parent(arg){
    let a=arg;
    return function children(){
        alert('a的值是:'+ a);
    }
}

function openClick(){
	setTimeout(()=>{
	    parent(111)();
	},3000);
}

function handleClick(){
    parent(222);
}
</script>

当点击【开始】按钮时,会使用定时器在三秒后调用闭包函数,页面弹出a的值111,如果在这三秒内点击了【改变a的值】按钮,触发事件修改闭包的外层函数的作用域下的a的值,但到3秒时页面弹出a的值仍然是111。

  • 21
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值