编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

react 使用注意

wxchong 2025-01-03 19:22:43 开源技术 591 ℃ 0 评论

Refs and the DOM

何时使用 Refs

下面是几个适合使用 refs 的情况:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

避免使用 refs 来做任何可以通过声明式实现来完成的事情。

举个例子,避免在 Dialog 组件里暴露 open() 和 close() 方法,最好传递 isOpen 属性。

勿过度使用 Refs

你可能首先会想到使用refs 在你的 app 中“让事情发生”。如果是这种情况,请花一点时间,认真再考虑一下 state 属性应该被安排在哪个组件层中。通常你会想明白,让更高的组件层级拥有这个 state,是更恰当的。---状态提升

你不能在函数组件上使用 ref 属性,因为他们没有实例。函数式组件里的dom元素是可以添加ref的,组件不能

在高阶组件中转发 refs

refs 将不会透传下去。这是因为 ref不是 prop 属性。就像 key一样,其被 React 进行了特殊处理。如果你对 HOC 添加 ref,该 ref 将引用最外层的容器组件,而不是被包裹的组件。

幸运的是,我们可以使用React.forwardRefAPI 明确地将 refs 转发到内部的组件或者元素


function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // 注意 React.forwardRef 回调的第二个参数 “ref”。
  // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
  // 然后它就可以被挂载到被 LogProps 包裹的子组件上。
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

Refs 与函数组件

默认情况下,你不能在函数组件上使用 ref 属性,因为它们没有实例:

如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle结合使用),或者可以将该组件转化为 class 组件


不管怎样,你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

回调 Refs

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = null;
    this.setTextInputRef = element => {
      this.textInput = element;
    };
    this.focusTextInput = () => {
      // 使用原生 DOM API 使 text 输入框获得焦点
      if (this.textInput) this.textInput.focus();
    };
  }
  componentDidMount() {
    // 组件挂载后,让文本框自动获得焦点
    this.focusTextInput();
  }
  render() {
    // 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
    // 实例上(比如 this.textInput)
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素

你可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

Render Props

术语 “render prop”是指一种在React 组件之间使用一个值为render函数的 prop 共享代码的简单技术

具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑。

<DataProvider render={data => (<h1>Hello {data.target}</h1>)}/>

使用 render prop 的库有 React Router、Downshift以及 Formik。

在这个文档中,我们将讨论为什么 render prop 是有用的,以及如何写一个自己的 render prop 组件。

使用 Render Props 来解决横切关注点(Cross-Cutting Concerns)

这也是 render prop 的来历:我们可以提供一个带有函数 prop 的 <Mouse> 组件,它能够动态决定什么需要渲染的,而不是将 <Cat> 硬编码到 <Mouse> 组件里,并有效地改变它的渲染结果。

import React, { PureComponent } from 'react'

class Cat extends React.Component {
    render() {
      const mouse = this.props.mouse;
      return (
        <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
      );
    }
}
  
class Mouse extends React.Component {
    constructor(props) {
        super(props);
        this.handleMouseMove = this.handleMouseMove.bind(this);
        this.state = { x: 0, y: 0 };
    }

    handleMouseMove(event) {
        this.setState({
            x: event.clientX,
            y: event.clientY
        });
    }

render() {
    return (
    <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/*
        Instead of providing a static representation of what <Mouse> renders,
        use the `render` prop to dynamically determine what to render.
        */}
        {this.props.render(this.state)}
    </div>
    );
}
}

class MouseTracker extends React.Component {
    render() {
        return (
            <div>
                <h1>移动鼠标!</h1>
                <Mouse render={mouse => (
                    <Cat mouse={mouse} />
                )}/>
            </div>
        );
    }
}
  
export default MouseTracker

重要的是要记住,render prop 是因为模式才被称为 renderprop ,你不一定要用名为 render的 prop 来使用这种模式。事实上, 任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 “render prop”

注意事项

将 Render Props 与 React.PureComponent 一起使用时要小心

如果你在 render 方法里创建函数,那么使用 render prop 会抵消使用 React.PureComponent 带来的优势。因为浅比较 props 的时候总会得到 false,并且在这种情况下每一个 render 对于 render prop 将会生成一个新的值。

为了绕过这一问题,有时你可以定义一个 prop 作为实例方法,类似这样:

class MouseTracker extends React.Component {
  // 定义为实例方法,`this.renderTheCat`始终
  // 当我们在渲染中使用它时,它指的是相同的函数
  renderTheCat(mouse) {
    return <Cat mouse={mouse} />;
  }

  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={this.renderTheCat} />
      </div>
    );
  }
}

如果你无法静态定义 prop(例如,因为你需要关闭组件的 props 和/或 state),则 <Mouse>应该扩展 React.Component。

React.memo

const MyComponent = React.memo(function MyComponent(props) {
  /* 使用 props 渲染 */
});

React.memo 为高阶组件。它与 React.PureComponent非常相似,但只适用于函数组件,而不适用 class 组件。

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


默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

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

与 class 组件中 shouldComponentUpdate()方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。

Children

map

React.Children.map(children, function[(thisArg)] )

<SafeAreaView style={style} edges={['right', 'bottom', 'left']}>
            {React.Children.map(children, child => child)}
 </SafeAreaView>

给props.children传参

React.Children.map(this.props.children, child => {
     return React.cloneElement(child, {
        params: () => {} // your props 
     });
 })

forEach

React.Children.forEach(children, function[(thisArg)])

与 React.Children.map()类似,但它不会返回一个数组。

count

React.Children.count(children)

返回 children中的组件总数量,等同于通过 map或 forEach调用回调函数的次数。

only

React.Children.only(children)

验证 children是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误

注意: React.Children.only() 不接受 React.Children.map()的返回值,因为它是一个数组而并不是 React 元素。

toArray

React.Children.toArray(children)

将 children 这个复杂的数据结构以数组的方式扁平展开并返回,并为每个子节点分配一个 key。当你想要在渲染函数中操作子节点的集合时,它会非常实用,特别是当你想要在向下传递 this.props.children 之前对内容重新排序或获取子集时。

注意:

React.Children.toArray() 在拉平展开子节点列表时,更改 key 值以保留嵌套数组的语义。也就是说,toArray 会为返回数组中的每个 key 添加前缀,以使得每个元素 key 的范围都限定在此函数入参数组的对象内。

lazy

React.lazy() 允许你定义一个 动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。

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

请注意,渲染 lazy组件依赖该组件渲染树上层的 <React.Suspense>组件。这是指定加载指示器(loading indicator)的方式。

注意 : 使用 React.lazy 的动态引入特性需要 JS 环境支持 Promise。在 IE11 及以下版本的浏览器中需要通过引入 polyfill 来使用该特性。

Suspense

React.Suspense可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。目前,懒加载组件是 <React.Suspense>支持的唯一

用例:

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

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

请注意,lazy 组件可以位于 Suspense 组件树的深处——它不必包装树中的每一个延迟加载组件。最佳实践是将 <Suspense> 置于你想展示加载指示器(loading indicator)的位置,而 lazy() 则可被放置于任何你想要做代码分割的地方。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表