Прошлое и настоящее React исх.

внешний интерфейс Element Алибаба React.js
Прошлое и настоящее React исх.

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

судейская тройка

До React v16.3 ref получали в виде строки (string ref) или callback-функции (callback ref).0017-new-create-refВ предложении представлен новый API React.createRef.

Примечание. Следующие примеры кода и исходный код в этой статье основаны на версии выпуска React v16.3.2 или получены из нее.

// string ref
class MyComponent extends React.Component {
  componentDidMount() {
    this.refs.myRef.focus();
  }
  render() {
    return <input ref="myRef" />;
  }
}

// callback ref
class MyComponent extends React.Component {
  componentDidMount() {
    this.myRef.focus();
  }
  render() {
    return <input ref={(ele) => {
      this.myRef = ele;
    }} />;
  }
}

// React.createRef
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  componentDidMount() {
    this.myRef.current.focus();
  }
  render() {
    return <input ref={this.myRef} />;
  }
}

Смерть ссылки на строку

До появления React.createRef, string ref долгое время подвергался критике.В официальном документе React прямо говорилось, что string ref будет удален в будущих версиях.Рекомендуется пользователям использовать вместо него callback ref.Зачем это нужно делать это? Основные причины следующие:

  • Когда ref определен как строка, React должен отслеживать текущий компонент рендеринга.На этапе согласования, во время создания и обновления элемента React, ref будет инкапсулирован как функция закрытия, ожидающая выполнения фазы фиксации, которая повлияет на производительность React.
function coerceRef(
  returnFiber: Fiber,
  current: Fiber | null,
  element: ReactElement,
) {
  ...
  const stringRef = '' + element.ref;
  // 从 fiber 中得到实例
  let inst = ownerFiber.stateNode;
  
  // ref 闭包函数
  const ref = function(value) {
    const refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;
    if (value === null) {
      delete refs[stringRef];
    } else {
      refs[stringRef] = value;
    }
  };
  ref._stringRef = stringRef;
  return ref;
  ...
}
  • При использовании режима обратного вызова рендеринга использование ссылки на строку вызовет неоднозначность в том месте, где эта ссылка смонтирована.
class MyComponent extends Component {
  renderRow = (index) => {
    // string ref 会挂载在 DataTable this 上
    return <input ref={'input-' + index} />;

    // callback ref 会挂载在 MyComponent this 上
    return <input ref={input => this['input-' + index] = input} />;
  }
 
  render() {
    return <DataTable data={this.props.data} renderRow={this.renderRow} />
  }
}
  • строка ref не может быть объединена, например, родительский компонент сторонних библиотек был передан в подсборки ref, тогда мы не сможем добавить ref на дочерний компонент, и callback ref может быть идеальным решением к этой проблеме.
/** string ref **/
class Parent extends React.Component {
  componentDidMount() {
    // 可获取到 this.refs.childRef
    console.log(this.refs);
  }
  render() {
    const { children } = this.props;
    return React.cloneElement(children, {
      ref: 'childRef',
    });
  }
}

class App extends React.Component {
  componentDidMount() {
    // this.refs.child 无法获取到
    console.log(this.refs);
  }
  render() {
    return (
      <Parent>
        <Child ref="child" />
      </Parent>
    );
  }
}

/** callback ref **/
class Parent extends React.Component {
  componentDidMount() {
    // 可以获取到 child ref
    console.log(this.childRef);
  }
  render() {
    const { children } = this.props;
    return React.cloneElement(children, {
      ref: (child) => {
        this.childRef = child;
        children.ref && children.ref(child);
      }
    });
  }
}

class App extends React.Component {
  componentDidMount() {
    // 可以获取到 child ref
    console.log(this.child);
  }
  render() {
    return (
      <Parent>
        <Child ref={(child) => {
          this.child = child;
        }} />
      </Parent>
    );
  }
}
  • Не работает при использовании на корневом компоненте.
ReactDOM.render(<App ref="app" />, document.getElementById('main')); 
  • Это менее дружелюбно к статическим типам.При использовании ссылки на строку тип ссылки должен быть явно объявлен, и автоматический вывод не может быть завершен.

  • Компилятор не может спутать ссылки на строки с их соответствующими свойствами в ссылках, но с ссылками обратного вызова они могут.

createRef vs callback ref

Сравнивая новый createRef и callback ref, нет подавляющего преимущества, но я надеюсь, что это станет удобной фичей, и будет небольшое преимущество в производительности. процесс рендеринга компонента. , а createRef принимает объект ref.

createRef более интуитивно понятен, похож на строку ref, что позволяет избежать некоторых проблем с пониманием callback ref.Для callback ref мы обычно используем форму встроенной функции, тогда она будет воссоздаваться каждый раз при рендеринге, потому что react очистит старый ref и установить новый (см. рисунок ниже, commitDetachRef -> commitAttachRef), так он будет вызываться дважды при обновлении, причем первый раз будет null.Если в callback есть бизнес-логика, он может пойти не так. Конечно, вы можете определить обратный вызов как функцию-член класса и выполнить привязку таким образом, чтобы избежать.

class App extends React.Component {
  state = {
    a: 1,
  };
  
  componentDidMount() {
    this.setState({
      a: 2,
    });
  }
  
  render() {
    return (
      <div ref={(dom) => {
        // 输出 3 次
        // <div data-reactroot></div>
        // null
        // <div data-reactroot></div>
        console.log(dom);
      }}></div>
    );
  }
}

class App extends React.Component {
  state = {
    a: 1,
  };

  constructor(props) {
    super(props);
    this.refCallback = this.refCallback.bind(this);
  }
  
  componentDidMount() {
    this.setState({
      a: 2,
    });
  }

  refCallback(dom) {
    // 只输出 1 次
    // <div data-reactroot></div>
    console.log(dom);
  }
  
  render() {
    return (
      <div ref={this.refCallback}></div>
    );
  }
}

Тем не менее, я должен признать, что createRef все еще уступает callback ref по своим возможностям, например, для проблемы комбинирования, упомянутой в предыдущем разделе, createRef бессилен. В React v16.3 обработка строки ref/callback ref и createRef немного отличается, давайте посмотрим на весь процесс создания ref.

// markRef 前会进行新旧 ref 的引用比较
if (current.ref !== workInProgress.ref) {
  markRef(workInProgress);
}

// effectTag 基于位操作,其中有 ref 的变更标志位
function markRef(workInProgress: Fiber) {
  workInProgress.effectTag |= Ref;
}
  
// effectTag 与 Ref 的 & 操作表示当前 fiber 有 ref 变更
if (effectTag & Ref) {
  commitAttachRef(nextEffect);
}

function commitAttachRef(finishedWork: Fiber) {
  const ref = finishedWork.ref;
  if (ref !== null) {
    const instance = finishedWork.stateNode;
    let instanceToUse;
    switch (finishedWork.tag) {
      // 当前 Host 环境为 DOM 环境,HostComponent 即为 DOM 元素,需要借助实例获取原生 DOM 元素
      case HostComponent:
        instanceToUse = getPublicInstance(instance);
        break;
      // 对于 ClassComponent 等而言,直接返回实例即可
      default:
        instanceToUse = instance;
    }
    // string ref 与 callback 都会去执行 ref 闭包函数
    // createRef 会直接挂在 object ref 的 current 上
    if (typeof ref === 'function') {
      ref(instanceToUse);
    } else {
      ref.current = instanceToUse;
    }
  }
}

Вышеупомянутое будет включать в себя некоторые концепции и детали реактивного волокна, такие как: значение объекта волокна, процесс построения и обновления дерева волокон, значение и процесс сбора effectTag и т. д. Если читатель не знаком с приведенными выше подробностями, вы можете временно пропустить это. абзац, не влияя на усвоение и понимание исх.

Облачная стрела React.forwardRef

В дополнение к createRef, React16 также предоставляет API React.forwardRef для ref, который в основном используется для прямого получения ref дочерних элементов через родительский элемент. Прежде чем говорить о сценарии использования forwardRef, давайте рассмотрим проблему HOC (компонента более высокого порядка) при использовании ref.Реф HOC нельзя передать через пропсы, поэтому напрямую получить обернутый компонент (WrappedComponent) невозможно, требуется передача.

function HOCProps(WrappedComponent) {
  class HOCComponent extends React.Component {
    constructor(props) {
      super(props);
      this.setWrappedInstance = this.setWrappedInstance.bind(this);
    }
    
    getWrappedInstance() {
      return this.wrappedInstance;
    }

    // 实现 ref 的访问
    setWrappedInstance(ref) {
      this.wrappedInstance = ref;
    }
    
    render() {
      return <WrappedComponent ref={this.setWrappedInstance} {...this.props} />;
    }
  }

  return HOCComponent;
}

const App = HOCProps(Wrap);

<App ref={(dom) => {
  // 只能获取到 HOCComponent
  console.log(dom);
  // 通过中转后可以获取到 WrappedComponent
  console.log(dom.getWrappedInstance());
}} />

После того, как у вас есть forwardRef, вам больше не нужно проходить через getWrappedInstance.Использование forwardRef может напрямую проникнуть в HOCComponent, чтобы получить WrappedComponent.

function HOCProps(WrappedComponent) {
  class HOCComponent extends React.Component {
    render() {
      const { forwardedRef, ...rest } = this.props;
      return <WrappedComponent ref={forwardedRef} {...rest} />;
    }
  }

  return React.forwardRef((props, ref) => {
    return <HOCComponent forwardedRef={ref} {...props}  />;
  });
}

const App = HOCProps(Wrap);

<App ref={(dom) => {
  // 可以直接获取 WrappedComponent
  console.log(dom);
}} />

Принцип React.forwardRef на самом деле очень прост, forwardRef сгенерирует специальный компонент внутри реакции. При создании и обновлении операций свойства и ссылки в компоненте forwardRef напрямую передаются функции рендеринга, введенной заранее для создания дочерних элементов.

const nextChildren = render(workInProgress.pendingProps, workInProgress.ref);

Здесь представлены все React refs.В новой версии React 16 представлены два новых API, React.createRef и React.forwardRef.Есть планы удалить старые строковые refs, чтобы сделать использование refs более удобным и понятным. Если ваше приложение было обновлено до React 16.3+, не стесняйтесь использовать React.createRef.Если нет, рекомендуется использовать callback ref вместо string ref.

В настоящее время наша команда тщательно изучает React16. Мы приглашаем партнеров сообщества обсудить и двигаться вперед вместе с нами. Если вы хотите присоединиться к нам, вы можете пообщаться в частном чате или отправить свое резюме по адресуdancang.hj@alibaba-inc.com.