阅读 461

React-如何优雅的写一个组件

简单组件-jsx

class HelloMessage extends React.Component {
  render() {
    return (
      <div>
        Hello {this.props.name}
      </div>
    );
  }
}
ReactDOM.render(
  <HelloMessage name="Taylor" />,
  document.getElementById('hello-example')
);
复制代码

有状态组件-jsx

使用外部数据(通过 this.props 访问) 组件还可以维护其内部的状态数据(通过 this.state 访问) 当组件的状态数据改变时,组件会再次调用 render() 方法重新渲染对应的标记。 注意点: setState 接口一个function为同步, 接受其他类型参数为异步

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

  tick() {
    this.setState(state => ({
      seconds: state.seconds + 1
    }));
  }

  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  render() {
    return (
      <div>
        Seconds: {this.state.seconds}
      </div>
    );
  }
}

ReactDOM.render(
  <Timer />,
  document.getElementById('timer-example')
);

复制代码

函数组件(无状态)

输出的,是可以预知的

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}
复制代码

汇总的几个区别点: 如下,敬请反驳:

首先:是面向对象和函数式编程这两套不同的设计思想之间的差异。(函数组件更加契合 React 框架的设计理念)

image.png

React 组件本身的定位就是函数,一个吃进数据、吐出 UI 的函数。 React 框架的主要工作,就是及时地把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。这就意味着从原则上来讲,React 的数据应该总是紧紧地和渲染绑定在一起的,而类组件做不到这一点。

其次:“函数组件会捕获 render 内部的状态,这是两类组件最大的不同” demo:codesandbox.io/s/pjqnl16lm…

虽然 props 本身是不可变的,但 this 却是可变的,this 上的数据是可以被修改的

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

Hooks 的本质:一套能够使函数组件更强大、更灵活的“钩子”

useState:设置初始化值。

一个问题:如果后面一个state 依赖前一个state的新值。则如何处理?

import React, { useState, useEffect } from "react";

export default function Counter() {
  const [count, setState] = useState(1);
  const [count2, setState2] = useState(1);

  useEffect(() => {});
  return (
    <>
      Count: {count} {count2}
      <button onClick={() => setState(1)}>Reset</button>
      <button
        onClick={() => {
          setState(count - 1);
          setState2(count);
        }}
      >
        -
      </button>
      <button
        onClick={() => {
          setState(count + 1);
          setState2(count);
        }}
      >
        +
      </button>
    </>
  );
}

复制代码

useEffect : effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。 基本上90%的情况下,都应该用这个,这个是在render结束后,你的callback函数执行,但是不会block browser painting,算是某种异步的方式吧,但是class的componentDidMount 和componentDidUpdate是同步的,在render结束后就运行,useEffect在大部分场景下都比class的方式性能更好.

useEffect(()=>{
    return ()=>{
       // 组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数
    }
},[])
复制代码
useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
复制代码

useLayoutEffect: 这个是用在处理DOM的时候,当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.

useRef:获取组件实例对象或者DOM对象,映射,浅拷贝,‘.current’,监听dom某个节点。

forwardref:用于父子组件传值,传递引用,参数是{props,res} -ref,useRef. 子组件接收父组件ref节点

useImperativeHandle: 决定ref组件能接受到的方法和属性

useContext:让你能够读取 context 的值以及订阅 context 的变化,需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context并且在组件外部创建一个公用creteContext,组件之间共享状态

useHook:自定义hook,需要在函数前面加上use关键词

性能优化:

useReducer:类似于redux-reducer,用于处理复杂的state逻辑并且包含多个子值,是usestate的替换方案,使用useReduser还会给触发深更新组件做性能优化浅渲染

import React, { useReducer } from "react";

function init(initialCount) {
  return { count: initialCount, count2: initialCount };
}

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1, count2: state.count + 1 };
    case "decrement":
      return { count: state.count - 1, count2: state.count + 1 };
    case "reset":
      return init(action.payload);
    default:
      throw new Error();
  }
}

export default function Counter({ initialCount }) {
  const [state, dispatch] = useReducer(reducer, 1, init);
  return (
    <>
      Count: {state.count} {state.count2}
      <button
        onClick={() => dispatch({ type: "reset", payload: initialCount })}
      >
        Reset
      </button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}

复制代码

useMemo:设置依赖项数组,只有当依赖项数组发生改变才会执行参数函数,解决重复渲染,解决性能优化

useCallback:用来缓存函数的

React 团队面向开发者给出了两条 React-Hooks 的使用原则,原则的内容如下:

只在 React 函数中调用 Hook;

不要在循环、条件或嵌套函数中调用 Hook。


import React, { useState } from "react";

// isMounted 用于记录是否已挂载(是否是首次渲染)

let isMounted = false;

function PersonalInfoComponent() {
  // 定义变量的逻辑不变

  let name, age, career, setName, setCareer;

  // 这里追加对 isMounted 的输出,这是一个 debug 性质的操作

  console.log("isMounted is", isMounted);

  // 这里追加 if 逻辑:只有在首次渲染(组件还未挂载)时,才获取 name、age 两个状态

  if (!isMounted) {
    // eslint-disable-next-line

    [name, setName] = useState("修言");

    // eslint-disable-next-line

    [age] = useState("99");

    // if 内部的逻辑执行一次后,就将 isMounted 置为 true(说明已挂载,后续都不再是首次渲染了)

    isMounted = true;
  }

  // 对职业信息的获取逻辑不变

  [career, setCareer] = useState("我是一个前端,爱吃小熊饼干");

  // 这里追加对 career 的输出,这也是一个 debug 性质的操作

  console.log("career", career);

  // UI 逻辑的改动在于,name 和 age 成了可选的展示项,若值为空,则不展示

  return (
    <div className="personalInfo">
      {name ? <p>姓名:{name}</p> : null}

      {age ? <p>年龄:{age}</p> : null}

      <p>职业:{career}</p>

      <button
        onClick={() => {
          setName("秀妍");
        }}
      >
        修改姓名
      </button>
    </div>
  );
}

export default PersonalInfoComponent;


复制代码

Hooks 的正常运作,在底层依赖于顺序链表

文章分类
前端
文章标签