React 基础实践 + 常用Api解读

318 阅读8分钟

规范写法;合理处理细节问题

在项目开发中,稍微注意下,有时就能避免不必要的开销,达到优化的目的。

1. 绑定事件尽量不要使用箭头函数

面临问题

react的更新主要来自于props改变的被动渲染,和state改变的主动渲染,当这两个条件都未改变时,如果我们给组件绑定箭头函数的事件,这会导致该组件每次都会被 render,即便是 PureComponent

<User onDelUser={() => handleDelUser(user.id)} />

当传递给 User 组件的 props 没有任何变动时,组件不会重新渲染。但是由于传递的是匿名箭头函数,所以父组件每次 render 的时候都会去重新定义一个新的函数传递给 User 组件,其 props 在父组件的每次重新渲染中都会变动,也就导致了不必要的渲染开销。

2. 循环正确使用key

为什么使用key?

react利用key来识别组件,它是一种身份标识,相同的keyreact认为是同一个组件,这样后续相同的key对应组件都不会被创建 有了key属性后,就可以与组件建立了一种对应关系,react根据key来决定是销毁重新创建组件还是更新组件。

错误用法

  1. index作为key
  2. index 拼接其他字段作为key;如果有元素移动或者删除,那么就失去了一一对应关系,剩下的节点都不能有效复用。

正确用法

使用唯一 id 作为key,能够做到有效复用元素节点。

3. 结合 React.Suspenselazy, 动态加载组件

// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    // 显示 <Spinner> 组件直至 OtherComponent 加载完成
    <React.Suspense fallback={<Spinner />}>
      <div>
        <OtherComponent />
      </div>
    </React.Suspense>
  );
}

常用api解读

主要是开发中一些常用的api,目前 React 开发 Hooks 的使用十分普遍,本文业主要围绕Hooks的使用心得,和在开发中的实践来表述。

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

实际上react-hooks就是函数组件解决没有state,生命周期,逻辑不能复用的一种技术方案。当下的 react 开发中,hooks 的使用已然成为一种趋势。

React.Fragment

react中不允许一个组件返回多个节点元素,需要有一个公共的父级元素才可以。

return (
    <div>
        <a>1</a>
        <a>2</a>
        <a>3</a>
    </div>
)

但是很多时候我们不希望出现一个无意义的节点,就可以使用Fragment来代替,Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。

可以使用<></>短语法来简写,但如果是遍历的元素,只有Fragment可以支持key属性,简写方式不支持。

key 是唯一可以传递给 Fragment 的属性。

React.StrictMode

StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告并输出在控制台中。

严格模式检查仅在开发模式下运行;不会影响生产构建。可以为应用程序的任何部分启用严格模式,如果要全局开启,可以在App或者Router外包裹

import React from 'react';

function ExampleApplication() {
  return (
    <>
      <Header />
      <React.StrictMode>
          <ComponentOne />
          <ComponentTwo />
      </React.StrictMode>
      <Footer />
    </>
  );
}

在上述的示例中,会对 Header 和 Footer 组件运行严格模式检查。但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查。

StrictMode 目前有助于:

createElement

React 并不强制要求使用 JSX。当你不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。

每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。 JSX作为语法糖,可以让我们编写的代码更为简洁。

用 JSX 编写的代码:

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

ReactDOM.render(
  <Hello toWhat="World" />,
  document.getElementById('root')
);

不使用 JSX 的代码:

class Hello extends React.Component {
  render() {
    return React.createElement('div', null, `Hello ${this.props.toWhat}`);
  }
}

ReactDOM.render(
  React.createElement(Hello, {toWhat: 'World'}, null),
  document.getElementById('root')
);

forwardRef 和 useImperativeHandle

React.forwardRef 会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle应当与forwardRef 一起使用。

const FilterTemplate = forwardRef((props, ref) => {

    useImperativeHandle(ref, () => {
      return {
        showModal() {
          setIsOpen(true)
        },
        hideModal() {
          setIsOpen(false)
        },
      }
    })
})

const Index = () => {
    const filterTemplateRef = useRef(null)
    cosnt handleOpenModal = () => {
        filterTemplateRef.current?.showModal();
    }
    return (
        <>
            <FilterTemplate ref={filterTemplateRef} />
            <Button onClick={handleOpenModal}>打开Modal</Button>
        </>
    )
}

React.memo

React.memoPureComponent作用类似,可以用作性能优化,React.memo 是高阶组件,函数组件和类组件都可以使用, 与PureComponent的区别是 React.memo只能对props的情况确定是否渲染,而PureComponent是针对propsstate做浅层比较。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState useReduceruseContext的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。

memo接受两个参数:

  1. 被包裹的组件本身,
  2. 自定义的比较函数。该函数若返回 true 组件不渲染 , 返回 false 组件重新渲染。
function MyComponent(props) {
  /* 使用 props 渲染 */
}

const areEqual = (prevProps, nextProps) => {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, areEqual);

useRef

  1. 用于访问DOM refs
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
  1. 它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。 「ref」 对象是一个 current 属性可变且可以容纳任意值的通用容器,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。
const uuid = useRef('');
uuid.current = res.uuid;

// 可规避状态写在依赖数组里导致一直刷新
const [count, setCount] = useState(0);
const countRef = useRef();
countRef.current = count;
useEffect(() => {
    const id = setInterval(() => {
        console.log(countRef.current);
    }, 1000);
    return () => clearInterval(id);
}, []);
// 这样,count 的确不再被使用,而是用ref存储了一个在所有帧中共享的变量。
  • 关于回调ref 如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现,可以参照这个官方示例。

useMemo

返回函数的返回值

useMemo接受两个参数,第一个参数是一个函数,返回值用于产生保存值。 第二个参数是一个数组,作为dep依赖项,数组里面的依赖项发生变化,重新执行第一个函数,产生新的值

应用场景: 缓存一些值,避免重新执行上下文

const number = useMemo(()=>{
    /** ....大量的逻辑运算 **/
   return number
   // 只有 props.number 改变的时候,重新计算number的值。
},[ props.number ])

useCallBack

缓存函数

hooks对「有没有变化」这个点其实很敏感。如果一个effect内部使用了某数据或者方法。若我们依赖项不加上它,那很容易由于闭包问题,导致数据或方法,都不是我们理想中的那个它。如果我们加上它,很可能又会由于他们的变动,导致effect疯狂的执行。

一、 在组件内部,那些会成为其他useEffect依赖项的方法,建议用 useCallback包裹,或者直接编写在引用它的useEffect中。

二、 己所不欲勿施于人,如果你的function会作为props传递给子组件,请一定要使用 useCallback包裹,对于子组件来说,如果每次render都会导致你传递的函数发生变化,可能会对它造成非常大的困扰。同时也不利于react做渲染优化。

举例说明 在现代码点这里

父组件

export default function App() {
    const [number, setNumber] = useState(1);
    // 不会触发子组件更新
    const getInfo = useCallback((sonName) => {
        console.log(sonName);
    }, []);

    // 会触发子组件更新
    const getInfoNoCallback = (sonName) => {
        console.log(sonName);
    };

    return (
        <>
            <Counter getInfo={getInfo} />

            <h1>{number}</h1>

            {/* 点击按钮触发父组件更新 ,但是子组件没有更新 */}

            <button onClick={() => setNumber(number + 1)}>增加</button>

        </>
    );
}

子组件

image.png

useState

这是最常用的一个Hook了,使用方法就不再赘述了。这里说一下如何实现类组件的一些实用方法。

实现this.setState({}, () => {})第二个参数

  • 类组件中封装
  // 定义
  setStateSync(state) {
    return new Promise(resolve => {
      this.setState(state, () => {
        resolve();
      });
    });
  }
  // 使用
  const test = async () => {
      await this.setStateSync({})
      // 此处的state已经是实时的了
  } 
  • 主要看下函数组件通过自定义hook模拟

这样我们使用useState的时候就不用操心取值时异步的问题啦。

function useCallbackState(state: any) {
  const cbRef = useRef<any>();
  const [data, setData] = useState(state);

  useEffect(() => {
    cbRef.current && cbRef.current(data);
  }, [data]);

  return [
    data,
    function (val: any, callback: any) {
      // 把第二个参数的函数保存到ref中,setData后执行,你的函数拿到的就是最新的值啦
      cbRef.current = callback;
      setData(val);
    }
  ];
}

// 使用
const [num, setNum] = useCallbackState(0);

setNum(2, (data) => {
    console.log('num', data)
})