React 基础及核心总结/二(组件篇)

1. 内置组件

除HTML原生标签外,React还有一些内置的组件。

1)<React.StrictMode>

严格模式。StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。

Ps:严格模式检查仅在开发模式下运行;它们不会影响生产构建

你可以为应用程序的任何部分启用严格模式。

2)<React.Fragment>

是一个类似于 Vue 中的 <template> 一样的空元素,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。还有一种缩写语法<>something...</>,短语法无法传参。

Fragment 能够在不额外创建 DOM 元素的情况下,让 render() 方法中返回多个元素。

Ps:key 是唯一可以传递给 Fragment 的属性。未来我们可能会添加对其他属性的支持,例如事件。

import React, { Fragment } from 'react';

function ListItem({ item }) {
  return (
    <Fragment>
      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </Fragment>
  );
}

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        <ListItem item={item} key={item.id} />
      ))}
    </dl>
  );
}

// 或者
function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // Fragments should also have a `key` prop when mapping collections
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

3)<React.Suspense>

可以指定加载Loading指示组件,以防其组件树中的某些子组件尚未具备渲染条件。也是唯一一个可支持Loading指示的组件。

React.lazy()引入的动态组件是其支持的唯一用例。也就是说,渲染 lazy 组件依赖该组件渲染树上层的 <React.Suspense> 组件。

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

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

const Spinner = ()=>(<div>Loading...</div>)

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

4)<Profiler>

测量一个 React 应用多久渲染一次以及渲染一次的“代价”。 它的目的是识别出应用中渲染较慢的部分。

这个标签一般很少用。文档:https://zh-hans.reactjs.org/docs/profiler.html

这个元素在生产环境是被禁用的,因为比较吃性能。

2. 组件包含关系

组件之间的包含关系作用类似于Vue/Angular中的插槽(slot)。但这种包含关系相比于插槽更加容易理解。 React 元素本质就是对象(object),所以你可以把它们当作 props,像其他数据一样传递。在 React 中没有“插槽”这一概念的限制,你可以将任何东西作为 props进行传递。

注意:React 组件无继承。如果你想要在组件间复用功能,我们建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。组件可以直接引入(import)而无需通过 extend 继承它们。

1) 使用 children prop

可将子组件传递到外层组件中,这使得别的组件可以通过 JSX 嵌套,将任意组件作为子组件传递:

// 传递子组件
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

// 外层插槽,props.children为自有属性
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

2) 不使用 children

自行约定所插入的位置:将所需内容传入 props,并使用相应的 prop。这类似于Vue的具名插槽。

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

// 直接将元素标签作为props属性进行传递
function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

3. 多组件对象导出

注意:组件名必须使用大写字母开头!

import React from 'react';

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="blue" />;
}

标签名只能使用点语法(<A.B>),不能使用其他表达式(错误语法:<A[b]>)。如果要解决动态标签问题, 需要首先使用表达式将标签名赋值给一个大写字母开头的变量:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 正确!JSX 类型可以是大写字母开头的变量。
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

4. 组件通信

1)父子通信

父组件给子组件传递数据,使用属性进行传递,子组件通过props获取父组件传递过来的值。

export default function Accordion() {
  return (
      <Panel title="About" isActive={true}>
        With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
      </Panel>
  );
}

function Panel({ title, children, isActive }) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive && (
        <p>{children}</p>
      )}
    </section>
  );
}

2)子父通信

父组件将一个方法通过属性传递给子组件,子组件调用props中父组件传来的方法并传参,父组件即可在该方法中获取子组件传递过来的值。说白了父子子父之间的通信都是依赖props。这和其他的框架有所不同,不存在自定义事件及emit触发机制。实际上也更加简单。这也说明,React的props可以传递任何类型的数据。

import { useState } from "react";

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel
        title="About"
        index={activeIndex}
        isActive={activeIndex === 0}
        onShow={(val) => () => setActiveIndex(val)}
      >
        美国佐治亚州立大学生物医学研究所研究人员开展的一项新研究显示,一种新的通用流感疫苗用在小鼠身上可预防甲型和乙型流感病毒的不同变种。日前这项研究发表在《公共科学图书馆・病原体》杂志上。
      </Panel>
      <Panel
        title="Etymology"
        index={activeIndex}
        isActive={activeIndex === 1}
        onShow={(val) => () => setActiveIndex(val)}
      >
        “改革开放是当代中国发展进步的必由之路,是实现中国梦的必由之路。”党的十八大以来以习近平同志为核心的党中央,统筹国内国际两个大局,推动许多领域实现历史性变革、系统性重塑、整体性重构,领导全党全国人民开创了改革开放新局面。
      </Panel>
    </>
  );
}

function Panel({ title, index, children, isActive, onShow }) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={onShow(index === 0 ? 1 : 0)}>Show</button>
      )}
    </section>
  );
}

3)多组件共享状态

可以使用Redux等工具进行状态管理。

也可以将状态移动到组件的公共父级,然后通过props将其传递给它们。这被称为提升状态,利用hooks(useState()useReducer())封装状态是编写 React 代码时最常见的事情之一。

5. 高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。具体而言,高阶组件是参数为组件,在不改变传入的组件前提下,返回值为新组件的函数。类似于纯函数,没有副作用。

// 此函数接收一个组件...
function withSubscription(WrappedComponent, selectData) {
  // ...并返回另一个组件...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      // ...负责订阅相关的操作...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      // ... 并使用新数据渲染被包装的组件!
      // 请注意,我们可能还会传递其他属性
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}
加载中...
加载中...