Подробные сведения о новых функциях React v16 (2)

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

предисловие

Прежде чем писатьПодробные сведения о новых возможностях React v16 (1), Если вы не видели его раньше, вы можете сначала прочитать его.В первую очередь представлен относительно простой и базовый API v16, репозиторий кода находится в этой статье. Эта статья содержит:

  • Изменения в функциях жизненного цикла
  • Погрузитесь в базовую архитектуру волокна React v16, чтобы объяснить, почему
  • новыйContextAPI и его реализация с использованием простогоreact-redux

Базовое волокно React v16

, Facebook потратил почти год на переписывание почти всей базовой архитектуры React только для того, чтобы представитьfiber. Этоfiberчто это такое? В Google Translate это означает «волокно». Это можно понять так:fiberЭто концепция, вторичная по отношению к «процессу», то есть более детализированная программа управления. Конкретно: если вы хотите отрендерить компонент, так как js однопоточный, он должен быть полностью отрендерен, как показано на рисунке:

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

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

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

Когда компонент рендерится (обновляется), он делится на пре-рендер и пост-рендер, а время управления координацией волокна совпадает с моментом рендеринга.Если в это время есть задача с более высоким приоритетом, реакция отбрасывает все вычисления, сделанные этим компонентом (да, это не совсем так), компонент не будет перерендерен, пока эта задача не будет выполнена. Вышеупомянутые три жизненных цикла будут вызываться перед рендерингом (обновлением), если это чистая функция, то это нормально, но если это функция с побочными эффектами, то она может быть вызвана дважды, что противоречит желанию разработчика. Работа с функциями должна выполняться с осторожностью. Кроме того, это еще и для следующего асинхронного рендеринга.Теперь подход React заключается в том, чтобы полностью отказаться от этих трех функций, чтобы избежать ненужных побочных эффектов, и заменить их функциями с возвращаемыми значениями.

Упомянутый ранее StrictMode предназначен для преднамеренного двойного вызова метода, который вскоре станет устаревшим, для обнаружения побочных эффектов.

Изменения жизненного цикла (v16.3)

Начиная с версии 16.3, исходный API с тремя жизненными цикламиcomponentWillMount,componentWillUpdate,componentWillReceivePropsБудет объявлено устаревшим в пользу двух совершенно новых жизненных циклов:

  • static getDerivedStateFromProps
  • getSnapshotBeforeUpdate

static getDerivedStateFromPropsПрименение

Дословный перевод легко понять: «получить состояние из реквизита», что легко понять. Метод принимает два параметра:nextProps,prevState, возвращаемое значение — это состояние, представляющее обновление, а нуль может быть возвращен, чтобы указать отсутствие обновления (новая функция React,setState(null)значит нет обновлений). Этот метод будет вызываться при изменении реквизита (включая первый рендеринг и изменение реквизита), а возвращенное состояние объединяется с текущим состоянием.

Заметным отличием от других функций жизненного цикла является то, что это статический метод, что означает, что вы не можете передатьthisДоступ к экземпляру компонента, то есть вы не можете получить к нему прямой доступthis.state,this.propsи ряд методов. Если вы должны использовать его вthis, вы можете получить только Компонент вместо экземпляра. Откройте репозиторий кода вsrc/GetDerivedStateFromPropsНиже приведены связанные примеры, существующий исходный метод реализации и текущий метод, собранные вместе для сравнения: Старый_Потребитель.jsx:

export default class Consumer extends React.Component {
    state = {
        // 从 props 获取默认 state
        result: this.getResult(this.props.value)
    }
    componentWillReceiveProps(nextProps) {
        // 常用范式
        if (nextProps.value !== this.props.value) {
            this.setState({
                // 更新 state
                result: this.getResult(nextProps.value)
            })
        }
    }
    
    // props 到 state 的数据映射
    getResult = value => value * value
    
    handleChange = (e) => {
        this.props.eraseResult()
        this.setState({
            result: e.target.value
        })
    }
    
    render() {
        return (
            <input type='text'
                onChange={ this.handleChange }
                value={ this.state.result }></input>
        )
    }
}

New_Consumer.jsx:

export default class Consumer extends React.Component {
    state = {
        result: 0,
        // 必须存储 props.value 到 state 的副本,以便 getDerivedStateFromProps 取到 
        value: 0
    }
    // 新的方法,接收 nextProps 和 prevState
    static getDerivedStateFromProps(nextProps, prevState) {
        // prevState.value 相当于当前组件的 this.props.value,是存在 state 的副本
        if (prevState.value !== nextProps.value) {
            // 返回新的state(只需返回更新的部分,与 `setState` 相同)
            return {
                // 相当于上面的 getResult,但只有一处
                result: nextProps.value * nextProps.value,
                // 又一次保存副本
                value: nextProps.value
            }
        }
        // 返回 null 表示不更新,此函数最后一定需要返回值
        return null
    }
    // 以下都相同
    handleChange = e => {
        this.props.eraseResult()
        this.setState({
            result: e.target.value
        })
    }
    render() {
        return (
            <input type="text"
                onChange={this.handleChange}
                value={this.state.result}></input>
        )
    }
}

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

Это изменение очень смелое и полностью реализованоv = f(s)Философия дизайна React (v = вид, s = состояние). Прежде чем мы смогли пройтиcomponentWillReveivePropsЧтобы отслеживать изменения реквизита и изменять состояние для получения обновлений, это псевдообновление реквизита, больше похожее наv = f(s, 0.5p)(р = реквизит). Конечно, состояние очень важно, чтобы оно было разным в зависимости от разных реквизитов, но эта реализация недостаточно чистая, более чистый метод — это новый метод, то естьv = f(s(p)), то есть, если разработчик достаточно заботится о свойстве реквизита, оно должно храниться в состоянии. Но не слишком ли агрессивен этот дизайн? Это добавит в состояние избыточные данные, но сделает состояние менее чистым; если новый API спроектирован как метод экземпляра, его можно получить.this.propsЭто то же самое, что и раньше, что делает концепцию дизайна не полностью реализованной; я не знаю ответа, так как в качестве нового API команда React дала нам ответ, поэтому давайте сделаем это первым.

Новый жизненный цикл реализует эффект forwardRef.

последний семестрВ конце секции forwardRef оставлена ​​яма, а в репозитории кода есть соответствующие под forwardRefMockWithoutForwardRef.jsxКомпоненты, для достижения одной и той же функциональности (функции более высокого порядка и т. Д.), За исключением того, что настоящий пример можно разместить на государственном свойстве, а не переписать атрибут, должен быть размещен в состоянии, но понятно семантика, это действительно так Отказ После понимания вышеуказанного примера этот пример одинаково, не повторяют их.

getStapshotBeforeUpdateПрименение

Это экземплярный метод, дословно переводится как «сделать снимок перед обновлением», который можно рассматривать как альтернативуcomponentWillUpdateэффект. Эта функция будет вызываться после рендера и перед отправкой в ​​DOM, получая два параметраprevProps, prevState, в этот момент вы можете получить все предыдущие состояния и все обновленные состояния; любое возвращаемое значение функции будет передано в качестве третьего параметра вcomponentDidUpdate.

Честно говоря, необходимо использовать в реальной разработкеcomponentWillUpdateа такжеcomponentDidUpdateТам нет много раз, потому что в каждом обновлении, как разработчик, вы будете знать, где состояние изменяется и обновляется, и код, который должен быть записан в этих двух функциях, часто записывается логически.setStateДо и после того, это понятно. Поскольку есть очень мало примеров, я буду напрямую ссылаться на официальный пример сайта для написания небольшого демонстрации, код находится под оригинальным складом. Теперь опубликуйте основную часть кода:

// list` 条目增加,渲染的 `li` 也增加。但是滚动条位置不变
export default class List extends React.Component {
    listRef = null
    // 新的生命周期
    getSnapshotBeforeUpdate(prevProps, prevState) {
        // 如果 `props.list` 增加,将原来的 scrollHeight 存入 listRef
        if (prevProps.list.length < this.props.list.length) {
            return this.listRef.scrollHeight
        }
        return null
    } 
    // snapshot 就是 `getSnapshotBeforeUpdate` 的返回值
    componentDidUpdate(prevProps, prevState, snapshot) {
        if (snapshot !== null) {
            // scrollTop 增加新的 scrollHeight 和原来 scrollHeight 的差值,以保持滚动条位置不变
            this.listRef.scrollTop += this.listRef.scrollHeight - snapshot
        }
    } 
    
    setListRef = ref => (this.listRef = ref)

    render() {
        return (
            <ul ref={this.setListRef} style={{ height: 200, overflowY: 'scroll' }}>
                {this.props.list.map((n, i) => (
                    <li key={i}>{n}</li>
                ))}
            </ul>
        )
    }
}

Добавлять комментарии очень просто, но использование на самом деле очень простое; есть два способа написать унифицированный каталог в репозитории исходного кода, используя соответственноcomponentWillUpdateметод (А) и не используяcomponentWillUpdateа такжеgetSnapshotBeforeUpdateметод (Б). Следует сказать, что реализация метода (B) действительно уродлива и трудна для чтения, или более элегантно использовать два других метода. Код не выложен, можете глянуть, если интересно.

Новый контекстный API

Применение

Перейдите непосредственно к коду:

// React.createContext 是新的 API,接收一个任意值作为默认值,并返回一个 Context 对象
// Context 对象有两个属性:Provider 和 Consumer,均为 React 的 Component
const ColorContext = React.createContext('red')
const { Provider, Consumer } = ColorContext
// 将 Context.Provider 包裹在你想应用 Context 的组件上,可指定 `value` 值,否则将使用默认值
const Wrapper = () => (
    <Provider value='red'>
        <Text></Text>
    </Provider>
)

// 当要用到 Context 里的值时,用 Context.Consumer 包裹
// 里面的 chlidren 只能是一个render 函数,并且函数的第一个参数就是 Context 的值
const Text = () => (
    <Consumer>
        {color => (
            <p style={{ color }}>This is Red!</p>
        )}
    </Consumer>
)

На самом деле, использование довольно простое, порядок следующий:

  1. Создайте новый объект Context и укажите значения по умолчанию.
  2. Используйте, где вы хотите установить значение контекстаContext.Providerупаковано и переданоprops.valueНастройки
  3. использовать там, где он используетсяContext.ConsumerОбернутый и визуализированный через функцию, первый параметр функции - установленное значение контекста.

Следует отметить, что вContext.Consumer, дочерние элементы должны быть функцией, чтобы значение контекста можно было передать явно, использование относительно простое, а также поддерживается комбинирование (вложенность). Начиная с этого API, React официально отказался от использования контекста.

выполнитьreact-redux

Чтобы использовать избыточность в React, все должны использоватьreact-reduxбиблиотека.最重要,也是最常用的的 API 就是Providerа такжеconnectreact-reduxбиблиотека. Весь пример кода находится в оригинальном репозитории, если вы еще не знаете redux, то можете его пропустить.

Цели

Поскольку чаще всего используетсяProviderа такжеconnectЕсли вам не нужно слишком усложнять, вы выполнили эти две основные функции, и запустилось простое приложение react-redux.

Обзор API

ProviderДля самого верхнего компонента просто передайте редукциюstore, который используется для передачи контекста всем дочерним компонентам.storeЕсть несколько методов, которые нас больше беспокоят:

  1. getState:getState()Вернуть все содержимое состояния;
  2. dispatch: отправка действия может изменить состояние в магазине;
  3. subscribe: подписывайтесь на изменения состояния и возвращайте функцию, от которой можно отказаться.

connectПолучите несколько параметров, здесь нас интересуют только два основных:

  1. mapStateToProps: функция, которая передает все состояние и возвращает интересующее состояние;
  2. mapDispatchToProps: также является функцией, используемой для связи действия с отправкой и превращения его в свойства компонента.

пример достижения цели

Так как в редуксе слишком много шаблонных кодов, я не буду их здесь выкладывать, только целевой код:

const App = () => (
    <Provider store={store}>
        <Text></Text>
    </Provider>
)
// 装饰器语法,开发中连接的组件往往是 class
class Text extends React.Component {
    render() {
        // incCount 是 action,count 是 state 上的值
        return (
            <button onClick={this.props.incCount}>{this.props.count}</button>
        )
    }
}

Начинать

Мы нашли одну общую вещь: оба контекста, так и React-redux у провайдеров, которые, по крайней мере, имеют эту часть:

// mock-react-redux.jsx

import React from 'react'
// 本例不需要默认值
const {Provider: ContextProvider, Consumer: ContextCosumer} = React.createContext()

class Provider extends React.Component {
    render() {
        // 将 store 放入 context 中
        return (
            <ContextProvider value={this.props.store}>
                {React.Children.only(this.props.children)}
            </ContextProvider>
        )
    }
}

// 连接时就是在 Consumer 中,并取出 store
const connect = (mapStateToProps = state => state, mapDispatchToProps = () => {}) => Component => props => (
    <ContextConsumer>
        {store => (
            <Component
                {...(mapStateToProps(store.getState()))}
                {...mapDispatchToProps(store.dispatch)}
                {...props}>
            </Component>
        )}
    </ContextConsumer>
)

Если достаточно знакомы с компонентом высокого порядка, который является первым впечатлением. По завершению мы нашли рендер нормальный, но когда мы нажимаем просмотр и не можем найти обновление. Если точка останова в хранилище обнаружила, что redux действительно меняется, однако React не обновляется. Это потому, что мы не заметили обновления React setState, поэтому есть два пути:

  1. Сохранить состояние при обновленииsetStateуведомить React;
  2. непосредственно черезthis.forceUpdate()Принудительное обновление.

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

// 修改 Provider
class Provider extends React.Component {
    state = this.props.store.getState()
    listener = null
    componentDidMount() {
        const { subscribe, getState } = this.props.store 
        this.listener = subscribe(() => {
            // setState 通知 React 更新视图
            this.setState(getState())
        })
    }
    componentWillUnmount() {
        // 别忘了解除监听,否则可能引起内存泄露
        this.listener()
    }
    render() {
        // 这里要做相应修改,Consumer 内只关心 state 和 dispatch
        return (
            <ContextProvider value={{ state: this.state, dispatch: this.props.store.dispatch }}>
                {React.Children.only(this.props.children)}
            </ContextProvider>
        )
    }
}

// connect 相应修改
const connect = (mapStateToProps = state => state, mapDispatchToProps = () => {}) => Component => props => (
    <ContextConsumer>
        {({ state, dispatch }) => (
            <Component
                {...(mapStateToProps(state))}
                {...mapDispatchToProps(dispatch)}
                {...props}>
            </Component>
        )}
    </ContextConsumer>
)

Это завершает простойreact-redux. Может быть непосредственно помещен в существующие приложения и замененreact-reduxпакет, работает нормально. Конечно есть много недостатков, например Провайдер не оптимизирует производительность, надо написатьshouldComponentUpdate,connectКомпоненты более высокого порядка не имеют имен и т. д., но не влияют на основную функциональность.