QQ Music: Практика новых возможностей React v16

Element Тенсент React.js товар

Приветствую всех вОблако Tencent + сообщество, получить больше крупной технической практики Tencent по галантерее ~

Эта статья написанаТехническая команда QQ MusicОпубликован вКолонка «Облако + сообщество»

img

С тех пор, как команда React выпустила версию v16.0 в сентябре прошлого года, до версии v16.3, выпущенной только что в марте 2018 года, React последовательно запустила ряд новых функций-блокбастеров и улучшила исходные функции с высокой обратной связью. такие как проблема вложенности на уровне одного узла в методе рендеринга, обеспечение захвата ошибок жизненного цикла, возможность компонентов указывать рендеринг на любом узле DOM (портал), а также новейшие контекстный API и Ref API. После использования вышеуказанных новых функций в течение определенного периода времени мы делимся и обобщаем некоторые подробности в этой статье.

1. Оптимизация метода рендеринга

img

Чтобы соответствовать дереву компонентов React и дизайну структуры различий, верхний слой должен быть обернут как один узел в методе рендеринга () компонента.Поэтому при фактическом проектировании и использовании компонента всегда необходимо обращать внимание к углублению вложенного уровня, что является одним из часто критикуемых вопросов React. Например, следующая структура содержимого должна быть вложена в другой div, чтобы сделать ее единым узлом для возврата:

render() {
  return (
    <div>
      注:
      <p>产品说明一</p>
      <p>产品说明二</p>
    </div>
  );
}

Теперь, после обновления версии v16, эта проблема имеет новое улучшение, метод рендеринга может поддерживать возврат массива:

render() {
  return [
    "注:",
    <p key="t-1">产品说明一</h2>,
    <p key="t-2">产品说明二</h2>,
  ];
}

Это действительно на один слой меньше, но люди продолжают находить, что код все еще недостаточно лаконичен. Во-первых, узел TEXT должен быть заключен в кавычки. Во-вторых, поскольку это массив, каждое содержимое должно быть разделено запятыми. Кроме того, вам нужно вручную добавить ключ к элементу, чтобы облегчить сравнение. Больше не хочется писать JSX.

Итак, React v16.2 кует железо, пока оно горячо, и предоставляет более прямой метод, который называется Fragment:

render() {
  return (
    <React.Fragment>
      注:        
      <p>产品说明一</p>
      <p>产品说明二</p>
    </React.Fragment>
  );
}

Видно, что это нормальный метод записи односпального узла, который напрямую обернут содержание внутри. Но сам фрагмент не генерирует настоящие узлы DOM, поэтому он не приведет к увеличению уровня вложенности.

Кроме того, Fragment также предоставляет новое сокращение JSX >>:

render() {
  return (
    <>
      注:
      <p>产品说明一</p>
      <p>产品说明二</p>
    </>
  );}

Смотрится удобнее. Однако обратите внимание, что если вам нужно добавить ключевые реквизиты во фрагмент, сокращение не поддерживается (это единственный случай, когда фрагменту необходимо добавить реквизиты):

<dl>
  {props.items.map(item => (
    // 要传key用不了 <></>
    <Fragment key={item.id}>
      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </Fragment>
  ))}
</dl>

2. Границы ошибки

img

Граница ошибки относится к созданию компонента с функцией отлова ошибок путем определения метода componentDidCatch на компоненте.Ошибки, возникающие в процессе жизни вложенных компонентов, будут перехватываться им, а не подниматься наружу.Вся страница и дерево компонентов аварийно разбилось.

Например, в следующем примере компонент ErrorBoundary используется для защиты содержимого и захвата ошибок, а также для отображения пользовательского интерфейса при возникновении ошибки:

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }
  componentDidCatch(error, 
   {componentStack}
  ) {
    this.setState({
      error,
      componentStack,
    });
  }
  render() {
    if (this.state.error) {
      return (
        <>
          <h1>报错了.</h1>
          <ErrorPanel {...this.state} />
        </>
      );
    }
    return this.props.children;
  }
}

export default function App(){
  return (
    <ErrorBoundary>
      <Content />
    </ErrorBoundary>
  );
}

Следует отметить, что граница ошибок может отлавливать ошибки только в жизненном цикле (в willMount/render и других методах). Невозможно отловить асинхронные ошибки в обратных вызовах событий, чтобы отловить и охватить все сценарии, вам все равно нужно взаимодействовать с методами window.onerror, Promise.catch, try/catch и другими.

3. React.createPortal()

img

Этот API используется для рендеринга частей контента отдельно для указанного узла DOM. В отличие от использования ReactDom.render для создания нового дерева DOM, для «разделения» контента с помощью createPortal(), передача данных, жизненный цикл и даже всплывающая подсказка событий все еще существуют в исходной структуре дерева абстрактных компонентов.

class Creater extends Component {
  render(){
    return (
      <div onClick={() => 
        alert("clicked!")
      }>
        <Portal>
          <img src={myImg} />
        </Portal>
      </div>
    ); 
  }
}

class Portal extends Component {
  render(){
    const node = getDOMNode();
    return createPortal(
      this.props.children,
      node 
    ); 
  }
}

Например, приведенный выше код, поместив внутрьСодержимое отображается на отдельном узле. В фактической структуре DOM img был отделен от дерева DOM самого Создателя и существует в другом независимом узле. Но когда нажимается img, он по-прежнему волшебным образом вызывает событие onclick для div внутри Creater. На самом деле это зависит от прокси-сервера React и переписывает всю систему событий, чтобы синхронизировать логику всего дерева абстрактных компонентов.

4. Контекстный API

img

Context API существовал как недокументированная экспериментальная функция в предыдущих версиях, и все больше и больше голосов просили его улучшить, в версии 16.3 команда React переработала и выпустила новый официальный Context API.

Использование Context API упрощает передачу и совместное использование некоторых «глобальных» данных между компонентами, чтобы решить проблему, которая в прошлом заключалась в том, что для обмена общими данными между компонентами требовалось передавать избыточные реквизиты слой за слоем (детализация реквизитов). Например следующий код:

const HeadTitle = (props) => {
  return (
    <Text>
    {props.lang.title}
    </Text>;
  );
};

// 中间组件
const Head = (props) => {
  return (
    <div>
      <HeadTitle lang={props.lang} />
    </div>
  );
};

class App extends React.Component {
  render() {
    return (
      <Head lang={this.props.lang} />;
    );
  }
}

export default App = connect((state) => {
  return {
    lang:state.lang
  }
})(App);

Чтобы использовать языковой пакет, мы храним языковой конфигурацию в магазин через Redux Connect к вершине, но необходимо использовать только нижние подкомпоненты. Мы также невозможны добавлять только подключение только для каждого компонента, что вызывает дубликаты и неоперабельные обновления данных диска данных. Следовательно, промежуточная сборка нуждается в слое слоев, чтобы продолжать проходить, то есть так называемое сверление реквизита.

Для такого глобального, редко изменяемого обмена данными лучше использовать Context API для реализации:

Первый шаг, аналогичный магазину, мы можем создать контекст и добавить значения по умолчанию:

const LangContext = React.createContext({
  title:"默认标题"
});

Затем предоставьте контекстный доступ к дереву компонентов через провайдера на верхнем уровне. Здесь вы можете изменить данные в контексте, передав значение.При изменении значения весь контент в задействованном потребителе будет повторно отображаться:

class App extends React.Component {
  render() {
    return (
      <LangContext.Provider
        value={this.state.lang}
      >
        <Head />
      </LangContext.Provider>
    );
  }
}

Там, где необходимо использовать данные, они напрямую обертываются с помощью Context.Consumer, который может передаваться в функцию рендеринга, из которой во время выполнения могут быть получены данные Context.

const HeadTitle = (props) => {
  return (
    <LangContext.Consumer>
      {lang => 
        <Text>{lang.title}</Text>
      }
    </LangContext.Consumer>
  );
};

После этого промежуточные компоненты больше не нужно передавать слой за слоем, и пропсов становится намного меньше, что снижает количество ошибок, вызванных промежуточной утечкой, и код более освежается:

// 中间组件
const Head = () => {
  return (
    <div>
      <HeadTitle />
    </div>
  );
};

Итак, глядя на приведенный выше пример, можем ли мы напрямую использовать Context API для замены всех передач данных, включая удаление библиотек синхронизации данных, таких как redux? На самом деле это не уместно. Как упоминалось ранее, Context API следует использовать в сценариях, в которых данные необходимо совместно использовать по всему миру, и желательно, чтобы данные не менялись часто. Поскольку контекст, который существует как верхний уровень, при изменении данных легко заставить всех вовлеченных потребителей повторно отображать.

Например следующий пример:

render() {
  return (
    <Provider value={{
      title:"my title"
    }} >
      <Content />
    </Provider>
  );
}

На самом деле, каждый раз, когда вы выполняете рендеринг, значение здесь представляет собой новый объект. Это легко приведет к повторному выполнению рендеринга всеми потребителями и повлияет на производительность.

Поэтому не рекомендуется злоупотреблять контекстом.Для некоторых неглобальных бизнес-данных не рекомендуется предоставлять общий доступ к ним как к глобальному контексту на верхнем уровне, чтобы избежать чрезмерного вложения контекста и частого повторного рендеринга.

5. Исходный API

В дополнение к Context API в версии 16.3 также представлены два новых Ref API для упрощения управления и использования ссылок в компонентах.

Перед этим давайте взглянем на два способа использования ref.

// string命名获取
componentDidMount(){
  console.log(this.refs.input);
}
render() {
  return (
    <input 
    	ref="input"
    />
  );
}
// callback 获取
render() {
  return (
    <input 
    	ref={el => {this.input = el;}}
    />
  );
}

Первый строковый метод относительно ограничен и неудобен для передачи или динамического сбора данных между несколькими компонентами. Последний метод обратного вызова является рекомендуемым методом ранее. Но это немного громоздко писать, и может быть несколько вызовов для очистки в процессе обновления (обратный вызов получает null).

Чтобы упростить использование, в новой версии представлен API CreateRef для создания объекта ref, на который можно напрямую ссылаться после передачи его в ref компонента:

constructor(props) {
  super(props);
  this.input = React.createRef();
}
componentDidMount() {
  console.log(this.input);
}
render() {
  return <input ref={this.input} />;
}

Кроме того, ForwardRef API помогает упростить передачу ссылок между вложенными компонентами и компонентом в элемент, избегая проблемы this.ref.ref.ref.

Например, у нас есть обернутый компонент Button, и чтобы получить DOM-элемент настоящей кнопки внутри, нам нужно сделать следующее:

class MyButton extends Component {
  constructor(props){
    super(props);
    this.buttonRef = React.createRef();
  }
  render(){
    return (
      <button ref={this.buttonRef}>
        {props.children}
      </button>
    );
  }
}
class App extends Component {
  constructor(props){
    super(props);
    this.myRef = React.createRef();
  }
  componentDidComponent{
    // 通过ref一层层访问
    console.log(this.myRef.buttonRef);
  }
  render(){
    return (
      <MyButton ref={this.myRef}>
        Press here
      </MyButton>
    );
  }
}

В этом сценарии использование метода API forwardRef для «проникновения» может быть намного проще:

import { createRef, forwardRef } from "react";

const MyButton = forwardRef((props, ref) => (
  <button ref={ref}>
    {props.children}
  </button>
));

class App extends Component {
  constructor(props){
    super(props);
    this.realButton = createRef();
  }
  componentDidComponent{
    //直接拿到 inner element ref
    console.log(this.realButton);
  }
  render(){
    return (
    <MyButton ref={this.realButton}>
      Press here
    </MyButton>
    );
  }
}

Суммировать

Выше приведены некоторые из наиболее важных и полезных новых функций с момента выпуска React v 16. Оптимизация также улучшает процесс разработки. Кроме того, v16 имеет хорошее уменьшение размера пакета по сравнению с предыдущей версией, что также очень выгодно:

img

Кроме того, если вы хотите узнать больше о некоторых изменениях, таких как обновления жизненного цикла (getDerivedStateFromProps, getSnapshotBeforeUpdate) и оптимизации SSR (гидрат), а также о предстоящих тенденциях React Fiber (асинхронный рендеринг), вы можете щелкнуть, чтобы просмотреть исходный текст. узнать больше официальной информации.

Так много интересных функций, если вы все еще используете v15 или даже более ранние версии, поторопитесь и обновите свой опыт!


вопросы и ответы

Как перейти с jQuery на React.js?

Связанное Чтение

Использование и совместное использование React Native в национальном приложении K song

Разбор NewString и NewStringUtf для разработки Android Native

Практика React-Native Bracket


Эта статья была авторским разрешением Tencent Cloud + Community Release, оригинальная ссылка: https: //cloud.tencent.com/developer/article/1137778 fromsource = waitui?

Приветствую всех вОблако Tencent + сообществоИли обратите внимание на общедоступную учетную запись WeChat сообщества Yunjia (QcloudCommunity) и как можно скорее получите больше массовой технической практики галантерейных товаров ~