Подробные сведения о новых возможностях React v16 (1)

внешний интерфейс JavaScript API React.js

предисловие

Прошло полгода с тех пор, как React выпустил версию v16, а последней на данный момент является v16.3. Начиная с v16, было добавлено больше новых API. Можно сказать, что по сравнению с предыдущим дизайном чистого API изменения очень большие. Видно, что команда Facebook React решила большинство предыдущих проблем и теперь начинает разрабатывать новые API и добавлять новые функции для React. Благодаря переписыванию базовой архитектуры React Fiber, версия 16 включает в себя множество полезных новых функций, а также некоторые «изменения с перерывами», и в более поздних версиях их будет больше. существуетОфициальный блогХотя введение выше очень полное, из-за несвоевременного перевода ссылка остается неизменной, а некоторые места трудно понять. Поэтому я дам здесь краткое введение через некоторый код, а также сделаю запись для себя, чтобы проконсультироваться с API.

Код в репозитории содержит мои лучшие практики.Прежде чем читать статью, я надеюсь, что у вас есть определенное представление о React и ES6. Конечно, если нет, вы можете посмотреть. Если пример неуместен или ошибочен, добро пожаловатьЭкспресс-просмотры.

репозиторий кода

Код размещен нарепозиторий githubВыше есть демоверсии, соответствующие новым функциям, добро пожаловать!Правильно, я здесь, чтобы обмануть

Начинать

В связи с подготовкой к долгосрочному обновлению текущая версия v16. Так что после клонирования на локалку сначалаcd react16,Опять такиnpm i, npm start. доступlocalhost:9000Вы можете посмотреть демо (не обращайте внимания на мой уродливый CSS). Слева есть несколько маршрутов, соответствующих нескольким новым функциям и соответствующим демонстрациям.

ErrorBoundary (v16.0)

До React v16, если возникала ошибка при рендеринге, вся страница сразу вылетала. Если вы достаточно знаете о React, вы можете знать секретный API:unstable_handleError. Эту функцию можно использовать для обнаружения ошибок страниц, однако мало кто из разработчиков знает о ней, поскольку она не задокументирована. Теперь у нас есть новый, официальный, стабильный API:componentDidCatch. подобноtry catchто же самое, можно использовать для захватаrenderОшибки в процессе, часто используемые для обнаружения ошибок и отображения разных страниц, чтобы избежать сбоя всей страницы.

демо

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

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

код

Основной код новой функции находится в/src/ErrorBoundary/ErrorHandler.jsxВниз. Изначально, если не нужно переключать режимы и постоянно ловить ошибки,ErrorHanderДолжно выглядеть так:

import React from 'react'

class FakeHandler extends React.Component {
    state = {
        hasError: false
    }
    // 新的生命周期钩子
    componentDidCatch(error, info) {
        this.setState({
            hasError: true
        })
    }
    // 重置状态,与新 API 无关
    reset = () => {
        this.setState({
            hasError: false
        })
        this.props.reset()
    }
    render() {
        // 显示备用页面的核心代码,若有错误显示备用页面
        return this.state.hasError ? (
            <React.Fragment>
                <p>页面渲染发生错误,这是备用页面,可打开 console 查看错误</p>
                <div>
                    <button onClick={this.reset}>点击此处重置</button>
                </div>
            </React.Fragment>
        ) : (
            React.Children.only(this.props.children)
        )
    }
}

Сфокусируйся наcomponentDidCatchЭто предложение, если ошибка поймана, поставьте текущуюstate.hasErrorустановить какtrue,renderОн также обычно используется, чтобы определить, есть ли ошибка, а затем отобразить ее, что можно использовать для отображения альтернативной страницы.componentDidCatchМетод обработки можно использовать в качестве классического примера.

componentDidCatchПринимает два аргумента: выданная ошибкаerrorи сообщения об ошибкахinfo, теперь в инфо содержится только информация о стеке вызовов, что не очень полезно, т.к. React всегда печатает стек при возникновении ошибки. В будущем может быть добавлена ​​новая информация, подождем и посмотрим.

Поскольку этот метод можно разместить в любом компоненте, альтернативные страницы можно настроить в разных местах страницы.

Note: эта функция жизненного цикла не может фиксировать ошибки вне рендеринга. Например, следующие ошибки не могут фиксироваться и будут отображаться нормально.

class A extends React.Component {
     render() {
        // 此错误无法被捕获,渲染时组件正常返回 `<div></div>`
        setTimeout(() => {
            throw new Error('error')
        }, 1000)
        return (
            <div></div>
        )
    }
}

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

Документ на официальном сайте имеет китайскую версию, для получения более подробной информации см.Error Boundaries

Portal (v16.0)

API естьReactDOM.createPortal. Его можно просто понимать как «портал», то есть он может напрямую отображать любой узел DOM вне родительского компонента, который часто используется во всплывающих окнах, окнах подсказок и т. д., и поддерживает всплытие событий и его поведение. полностью совместим с дочерними компонентами. демо-код находится вsrc/PortalВниз. Обратите внимание, что этот метод нельзя вызывать произвольно, только в методе рендеринга компонента и как допустимый.elementвместо возвращения.

Note: новый API монтируется под реактивным домом, а не в пакете React.

Пример кода:

import React from 'react'
import { createPortal } from 'react-dom'
class Dialog extends React.Component {
    render() {
        // 一定要 return
        return createPortal((
            <div></div>
        ), document.querySelector('#dialog'))
    }
}

Фактически отображаемый DOM выглядит так, как показано, даже если все приложениеdiv#app, createPortal все еще может быть снаружиdiv#poralВизуализируйте элемент вниз.

Это очень просто, но будьте осторожны, чтобы не злоупотреблять.Так же, как ref, постарайтесь оставить все, что может сделать react, для react. Независимо от того, этот API очень прост в использовании, когда он используется как всплывающее окно, и это благо для интерфейсов, которые являются основными компонентами всплывающих окон. Для получения дополнительной информации обратитесь к официальной китайской документации.Portals.

Fragment(v16.0) & StrictMode(v16.3)

Эти два статических компонента монтируются под пакетом React черезReact.Fragmentа такжеReact.StrictModeдоступный.

Статические компоненты фрагментов, представленные в версии 16.0, используются для объединения нескольких React.renderВозвращаемое значение помещается в элемент верхнего уровня.element. Раньше, если нам нужно было вернуть несколько элементов, нам приходилось обертывать слой снаружи.<div></div>или другие элементы, React также отобразит их в настоящий DOM или напрямую вернет соответствующий массив (поддержка React v16.0), но очень уродливый и должен сопровождатьсяkeyимущества, даже если оно не используется.

Теперь новый Фрагмент используется только для накрутки, и не будет генерировать соответствующий DOM, как и обычный jsx, он не нуженkeyсвойства, это по-прежнему очень хорошая новая функция. Официальная документация:Fragments

Строгий режим был представлен в версии 16.3. Как следует из названия, строгий режим можно использовать для оповещения компонентов об устаревших и устаревших API в среде разработки (в этой версии устарели три хука жизненного цикла). Как и фрагмент, он не будет преобразован в настоящий DOM. официальная документациястрогий режимВ нем подробно описаны ситуации, в которых выдается предупреждение. Мы, разработчики, можем избежать этих предупреждений, если вовремя откажемся от нерекомендуемой записи.

Образец фрагмента кода и строгиsrc/NewComponentВниз:

import React, {Fragment, StrictMode} from 'react'

const FragmentItem = props => new Array(5).fill(null).map((k, i) => (
    <Fragment key={i}>
        <p>这是第{i}项</p>
        <p>{i} * {i} = {i * i}</p>
    </Fragment> 
))

class OldLifecycleProvider extends React.Component {
    // 以下三个函数在 React v16.3 已不被推荐,未来的版本会废弃。
    componentWillMount() {
        console.log('componentWillMount')
    }
    componentWillUpdate() {
        console.log('componentWillUpdate')
    }
    componentWillReceiveProps() {
        console.log('componentWillReceiveProps')
    }
    render() {
        return (
            <FragmentItem></FragmentItem>
        )
    }
}

export default class NewComponent extends React.Component {
    state = {
        propFlag: 2
    }
    // 使 OldLifecycleProvider 进入 componentWillReceiveProps 函数
    componentDidMount() {
        this.setState({
            propFlag: 1
        })
    }
    render() {
        return (
            <StrictMode>
                <OldLifecycleProvider propFlag={this.state.propFlag}></OldLifecycleProvider>
            </StrictMode>
        )
    }
}

Уровень рендеринга:NewComponent -> OldLifecycleProvider -> FragmentItem, вы можете видеть, что многослойная структура все еще видна под инструментом разработчика React (фрагмент не отображается, к сожалению, я надеюсь, что новая версия инструмента разработчика может решить эту проблему), но визуализированный уровень DOM по-прежнему плоский , установленный непосредственно наdiv.viewВниз.

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

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

Если вас очень беспокоит возможность обновления кода проекта в будущем, вы даже можете обернуть его с помощью StrictMode на верхнем уровне. Но на самом деле, кроме того, если проект стабилен, включение этого режима не принесет никакой пользы разработчикам, и даже возникнет дополнительная работа по миграции, поэтому в уже запущенных проектах его использовать не рекомендуется; очень полезен для рефакторинга кода. Всегда предупреждайте разработчиков о приближающихся устаревших API для миграции. Я считаю, что в экосистеме React он будет совместим с JS.'use strict'То же самое применяется все шире и шире.

createRef (v16.3)

ссылка на версию v15

В предыдущей версии, если вы хотели получить ссылку на элемент, было два варианта:

  • Строковая форма:<input ref="input" /> => this.refs.input
  • Форма функции обратного вызова:<input ref={input => (this.input = input)} /> => this.input

Среди них строковая форма из-за различных проблем (issue,Слухи:Этот парень является автором редукса) и не рекомендуется, конкретное содержание:

  1. ссылки, которые необходимо отслеживать внутриthisЗначение сделает React немного медленнее;
  2. иногдаthisЭто не то, что вы думаете:
import React from 'react'

class Children extends React.Component {
    componentDidMount() {
        // <h1></h1>
        console.log('children ref', this.refs.titleRef)
    }
    render() {
        return (
            <div>
                {this.props.renderTitle()}
            </div>
        )
    }
}

export default class Parent extends React.Component {
    // 放入子组件渲染
    renderTitle = () => (
        <h1 ref='titleRef'>{this.props.title}</h1>
    )
    componentDidMount() {
        // undefined
        console.log('parent ref:', this.refs.titleRef)
    }
    render() {
        return (
            <Children renderTitle={this.renderTitle}></Children>
        )
    }
}

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

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

Поэтому форма часто используемых функций практически не определена, жаль только, что новую функцию нужно создавать, если ее разместить в рендере, то это повлияет на производительность, если разместить под классом, то будет дополнительная функция, не связанная с бизнесом. Но теперь у нас есть новый API: createRef. Основное использование:

class A extends React.Component {
    inputRef = React.createRef()

    componentDidMount() {
        // 注意 current
        this.inputRef.current.focus()
    }

    render() {
        return (
            <input type="text" ref={this.inputRef}></input>
        )
    }
}

пройти черезthis.inputRef.currentможет быть получен.this.inputRefПо сути это прототипObject.prototypeобъекта, и пока есть только одинcurrentключ, соответствующее значение является полученным ref. Похоже, команда React зарезервировала интерфейс, и в следующей версии Ref добавят новые функции.

По сравнению со строковой формой,createRefНе только декларирует необходимость получения Ref заранее в кодировке, но и избегает всевозможных огрехов в виде строк, например, для функциональной формы можно написать на одну функцию меньше, но это недостаточно гибко, и функциональная форма все еще может потребоваться в фактическом кодировании, что также является документацией React. Причина, по которой функциональная форма указана как продвинутая техника в . Поэтому, как разработчик, вам нужно полностью избегать строковой формы и пытаться использоватьcreateRef, перечисляет функциональную форму в качестве альтернативы, а в версии 16.3 см.createRef, бессмысленныйcurrentВот и все.

ForwardRef (v16.3)

доForwardRefЭта концепция, специально разработанная для компонентов более высокого порядка, позволяет получить Ref. Официальная документация (на английском языке)Forwarding RefsВ примере смешано много введения и понимания высокоуровневых компонентов (HOC), что недостаточно чисто и не способствует предварительному пониманию ForwardRef.Простая концепция была усложнена.Ниже приведен простой пример для проиллюстрируйте его основное использование:

import React from 'react'

// 高阶组件,注意返回值用 `React.forwardRef` 包裹
// 里面的无状态组件接收第二个参数:ref
const paintRed = Component => React.forwardRef(
    // 此例中,ref 为 ForwardRef 中的 textRef
    (props, ref) => (
        <Component color='red' ref={ref} {...props}></Component>
    )
)

class Text extends React.Component {
    // 仅用于检测是否取到 ref
    value = 1
    render() {
        const style = {
            color: this.props.color
        }
        return (
            <p style={style}>
                我是红色的!
            </p>
        )
    }
}

const RedText = paintRed(Text)

export default class ForwardRef extends React.Component {
    textRef = React.createRef()
    componentDidMount() {
        // value = 1
        console.log(this.textRef.current.value)
    }
    render() {
        // 如果没有 forwardRef,那么这个ref只能得到 `RedText`,而不是里面的 `Text`
        return (
            <RedText ref={this.textRef}></RedText>
        )
    }
}

В этом примере forwardRef в основном предназначен для разработчиков компонентов высокого уровня, и процесс использования выглядит следующим образом:

  1. При написании компонентов более высокого порядка возвращаемый компонент без состояния оборачивается с помощью forwardRef, и можно передать второй параметр ref;
  2. Возвращаемые значения в компонентах без состояния могут передаваться в ref в качестве реквизита.

Параметры в forwardRef могут быть только компонентами без состояния.Что если возвращаемое значение компонента более высокого порядка не функция без состояния, а класс с функцией жизненного цикла? В официальной документации React уже есть пример, заключающийся в том, чтобы обернуть слой компонентов без состояния снаружи, а именно:

const paintRed = Component => (() => {
    // 新增 `componentDidMount` 
    class WhatEver extends React.Component {
        static displayName = `PaintRed(${Component.displayName || Component.name || Unkown})`
        componentDidMount() {
            console.log('Mounted!')
        }
        render() {
            // textRef 即为最外层的 ref
            const { textRef, ...props } = this.props
            return (
                <Component color='red' ref={textRef} {...props}></Component>
            )
        }
    }
    const forwardRef = React.forwardRef(
        // 这里再将 ref 的值作为普通 props 传递即可
        (props, ref) => (
            <WhatEver textRef={ref} {...props}></WhatEver>
        )
    )
    return forwardRef
})()

Как мы все знаем, два реквизита React являются закрытыми: key и ref, которые нельзя передать дочерним компонентам как обычные реквизиты. Однако, как видно из этого примера, функция forwardRef такова: обернутый компонент без состояния может получить ref в качестве второго параметра и может передать его дальше. На данный момент ref по-прежнему приват в пропсах, и его по-прежнему нельзя вытащить из пропсов, что по-прежнему не ломает оригинальный дизайн.

Если вы не используете createRef, а используете исходные две формы, это нормально.

Этот API дает мне ощущение, что он мало используется.На самом деле необходимо использовать ref в компоненте высокого порядка.Случаев очень мало, и большинство из них можно решить с помощью обычного API реакции, но он, наконец, устранил первоначальную слепую зону, поэтому его можно рассматривать только как новую функцию, которая лучше, чем ничего. Но на самом деле в документе также упоминалось, что большую часть времени, когда вам нужно использовать forwardRef, можно решить другими способами. Например, в приведенном выше репозитории исходного кода есть немного сложная демонстрация forwardRef, но на самом деле ту же функцию можно реализовать и без forwardRef, и она использует новую реализацию функции жизненного цикла, которая будет подробно описана далее. время, когда мы говорим о новых крюках жизненного цикла сказать.