Краткий обзор практики проектирования компонентов React 04 — Компонентное мышление

React.js
Краткий обзор практики проектирования компонентов React 04 — Компонентное мышление

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

Каталог серий


содержание




1. Компоненты высшего порядка

Долгое время компоненты более высокого порядка были самым популярным способом улучшения и компоновки компонентов React.Эта концепция возникла из функций более высокого порядка функционального программирования.Компоненты более высокого порядка можно определить как:Компоненты более высокого порядка — это функции, которые принимают исходный компонент и возвращают расширенную/заполненную версию исходного компонента.:

const HOC = Component => EnhancedComponent;

Сначала нам нужно понятьЗачем нужны компоненты более высокого порядка:

РеагироватьДокументациясказал очень четко,Компоненты более высокого порядка — это логический шаблон для повторного использования компонентов., Наиболее распространенным примером является редуксconnectи реактивный маршрутизаторwithRouter, Компоненты более высокого порядка изначально использовались для замены примесей (пониманиеПрошлое и настоящее React MixinРезюме - это две точки:

  • Повторное использование логики.Извлеките некоторую общую логику кода и поместите ее в компоненты более высокого порядка, чтобы больше компонентов могли совместно использовать
  • Разделение ответственности. В предыдущей главе упоминался принцип «разделения логики и представления». Компоненты более высокого порядка могут использоваться в качестве носителя для реализации этого принципа. Обычно мы выделяем уровень поведения или бизнес-уровень в компоненты более высокого порядка. чтобы реализовать, позвольте компонентам дисплея сосредоточиться только на пользовательском интерфейсе

Некоторые из компонентов более высокого порядкаРеализацияЕсть два основных:

  • 属性代理(Props Proxy): проксировать реквизиты, переданные обернутому компоненту, и работать с реквизитами. Этот метод используется чаще всего. Этот метод можно использовать для:

    • Реквизит для действий
    • Доступ к обернутому экземпляру компонента
    • состояние извлечения
    • Оберните обернутый компонент другими элементами
  • 反向继承(Inheritance Inversion): Компоненты более высокого порядка наследуют обернутый компонент, например:

    function myhoc(WrappedComponent) {
      return class Enhancer extends WrappedComponent {
        render() {
          return super.render();
        }
      };
    }
    

    можно реализовать:

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

На самом деле компоненты более высокого порядка могут делать больше, чем перечислено выше. Компоненты более высокого порядка очень гибкие и зависят от вашего воображения. Читатели могут понятьrecomposeЭта библиотека просто играет с высокоуровневыми компонентами.

Суммируйте компоненты высокого порядкаСценарии применения:

  • Управление реквизитом: добавление, удаление, проверка и изменение реквизита, например преобразование реквизита, расширение реквизита, исправление реквизита, переименование реквизита.
  • Внедрение зависимостей.Внедрение контекста или внешнего состояния и логики, таких как redux connnect, react-router withRouter.Старый контекст является экспериментальным API, поэтому многие библиотеки не сохраняют контекст, а внедряют его в виде компонентов более высокого порядка.
  • Расширить состояние: например, внедрить состояние в функциональные компоненты
  • Избегайте повторного рендеринга: например, React.memo
  • Отделяйте логику, держите компоненты тупыми

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

Некоторые из компонентов более высокого порядкаТехнические характеристики:

  • Отображаемое имя оболочки для упрощения отладки

    function withSubscription(WrappedComponent) {
      class WithSubscription extends React.Component {
        /* ... */
      }
      WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
      return WithSubscription;
    }
    
    function getDisplayName(WrappedComponent) {
      return WrappedComponent.displayName || WrappedComponent.name || 'Component';
    }
    
  • Используйте React.forwardRef для пересылки ссылки

  • Используйте «функции более высокого порядка» для настройки «компонентов более высокого порядка», что максимизирует возможность компоновки компонентов более высокого порядка.Подключение Redux является типичным примером.

    const ConnectedComment = connect(
      commentSelector,
      commentActions,
    )(Comment);
    

    Его преимущества могут быть реализованы при использовании compose для композиции:

    // 🙅 不推荐
    const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent));
    
    // ✅ 使用compose方法进行组合
    // compose(f, g, h) 和 (...args) => f(g(h(...args)))是一样的
    const enhance = compose(
      // 这些都是单独一个参数的高阶组件
      withRouter,
      connect(commentSelector),
    );
    
    const EnhancedComponent = enhance(WrappedComponent);
    
  • Перенаправить все нерелевантные реквизиты в обернутый компонент

    render() {
      const { extraProp, ...passThroughProps } = this.props;
      // ...
      return (
        <WrappedComponent
          injectedProp={injectedProp}
          {...passThroughProps}
        />
      );
    }
    
  • Именование: обычно именуется с помощью *, если оно содержит параметры, оно будет называться с помощью create*




2. Render Props

Render Props (Function as Child) также является распространенным режимом реагирования, таким как официальныйContext APIа такжеreact-springБиблиотека анимации Назначение компонентов более высокого порядка аналогично: все они предназначены для разделения задач и повторного использования логики компонентов, их проще использовать и реализовать, чем компоненты более высокого порядка, и в некоторых сценариях они могут заменить компоненты более высокого порядка. Официальное определение:

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

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

<FunctionAsChild>{() => <div>Hello,World!</div>}</FunctionAsChild>

Пример использования:

<Spring from={{ opacity: 0 }} to={{ opacity: 1 }}>
  {props => <div style={props}>hello</div>}
</Spring>

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

  • Плохая читаемость, особенно с несколькими уровнями вложенности.
  • Плохая композиция, можно вкладывать только слой за слоем через JSX, как правило, не более одного слоя.
  • Он подходит для динамического рендеринга.Поскольку он ограничен узлом JSX, текущему компоненту сложно получить данные, передаваемые реквизитами рендеринга.Если вы хотите передать их текущему компоненту, вам все равно придется передать их через props, то есть передать его через компоненты более высокого порядка

Откройте свой разум снова, сделайте запрос интерфейса через компонент Fetch:

<Fetch method="user.getById" id={userId}>
  {({ data, error, retry, loading }) => (
    <Container>
      {loading ? (
        <Loader />
      ) : error ? (
        <ErrorMessage error={error} retry={retry} />
      ) : data ? (
        <Detail data={data} />
      ) : null}
    </Container>
  )}
</Fetch>

До появления React Hooks этот шаблон часто использовался для добавления состояния к функциональным компонентам (или к простым компонентам).react-powerplug

официальныйДокументация




3. Используйте компоненты для абстрагирования бизнес-логики

В большинстве случаев представление компонента представляет собой объект пользовательского интерфейса.На самом деле компонент может не только представлять пользовательский интерфейс, но также может использоваться для абстрагирования бизнес-объектов.Иногда абстракция как компонент может тонко решить некоторые проблемы.

Например: когда утверждающий утверждает запрос, запрашивающий не может повторно редактировать; в противном случае, когда инициатор редактирует, утверждающий не может утвердить. Это механизм блокировки, и серверная часть обычно использует механизм пульса для поддержания этого ' lock", эта блокировка может быть снята явно или автоматически, если она не будет активирована в течение определенного периода времени, например при закрытии страницы. Таким образом, внешний интерфейс обычно использует механизм опроса для активации блокировки.

Общая реализация:

class MyPage extends React.Component {
  public componentDidMount() {
    // 根据一些条件触发, 可能还要监听这些条件的变化,然后停止加锁轮询. 这个逻辑实现起来比较啰嗦
    if (someCondition) {
      this.timer = setInterval(async () => {
        // 轮询
        tryLock();
        // 错误处理,可以加锁失败...
      }, 5000);
    }
  }

  public componentWillUnmount() {
    clearInterval(this.timer);
    // 页面卸载时显式释放
    releaseLock();
  }

  public componentDidUpdate() {
    // 监听条件变化,开始或停止锁定
    // ...
  }
}

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

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

/**
 * 锁定器
 */
const Locker: FC<{ onError: err => boolean, id: string }> = props => {
  const {id, onError} = props
  useEffect(() => {
    let timer
    const poll = () => {
      timer = setTimeout(async () => {
        // ...
        // 轮询,处理异常等情况
      }, 5000)
    }

    poll()

    return () => {
      clearTimeout(timer)
      releaseLock()
    }
  }, [id])

  return null
};

Использовать шкафчик

render() {
  return (<div>
    {someCondition && <Locker id={this.id} onError={this.handleError}></Locker>}
  </div>)
}

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




4. Хуки заменяют компоненты более высокого порядка

Лично я думаю, что хуки — это революционная функция для разработки React, она меняет мышление и модель разработки.Первое, что нужно спросить: «Какую проблему они решают? Что нового они привносят?»

Хуки первыми решают болевые точки компонентов более высокого порядка или Render Props.мотивация' он сказал:

    1. Сложно повторно использовать логику состояния между компонентами.:
    • Проблема: Сама структура React не предоставляет способа/примитива для внедрения многократно используемой логики в компоненты.RenderProps и компоненты более высокого порядка - это просто вещи на уровне шаблона (или уровня языка):

    • Предыдущее решение: Компоненты более высокого порядка и Реквизиты рендеринга.Эти схемы основаны на механизме самого компонента

      • Компоненты более высокого порядка и Render Props вызовут избыточное вложение узлов.
      • Требуется настройка структуры компонентов, что делает код громоздким и трудным для понимания.
      • Компоненты более высокого порядка сложны и трудны для понимания
      • Раньше у компонентов высокого порядка также были проблемы с пересылкой ссылок и т. д.
    • Как разрешаются хуки:

      • Отделяет логику состояния от компонентов, чтобы ее можно было независимо протестировать и повторно использовать.
      • Хуки могут быть разделены между компонентами, не затрагивая структуру компонента.

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

      • На практике трудно разбить эти компоненты на более мелкие, потому что состояние присутствует повсюду. Проверить их тоже сложно.
      • Часто приводит к чрезмерной абстракции, такой как избыточность, которая требует перехода в несколько файлов, требует большого количества файлов шаблонов и кода шаблона.
    • Предыдущее решение: Компоненты более высокого порядка и Render Props или менеджеры состояний Разделить логику абстракции и пользовательский интерфейс на более мелкие компоненты

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


    1. Компоненты на основе классов не являются ни машинными, ни удобными для пользователя:
    • вопрос:
      • Для людей: нужно понимать это, код многословен
      • Для машин: плохо оптимизирован
    • Как решаются хуки: функциональные компоненты
    • Новый вопрос: вам нужно понимать замыкания

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

migrate to hooks

Картинка из твиттера(@sunil Pai)

  • Новый способ написания компонентов, В отличие от предыдущих методов разработки, основанных на классах или чисто функциональных компонентах, хуки предоставляют более лаконичный API и механизм повторного использования кода, что делает код компонента короче.Например 👆 На картинке выше показано сравнение структуры кода миграции на хуки, читатели могут также посмотрите это выступление (90% Cleaner React).

  • Более детальное управление состоянием (useState), Ранее у компонента был только один setState для централизованного управления состоянием компонента,Теперь хуки, как и компоненты, представляют собой агрегированную единицу логики и состояния, а это значит, что разные хуки могут поддерживать свое состояние..

  • Будь то хук или компонент, это обычная функция.

    • Компоненты и хуки в некоторой степени однородны (оба содержат состояние и логику)Унифицированное использование разработки функциональных форм, благодаря которому вам не нужно переключаться между классами, компонентами более высокого порядка или контекстом renderProps, что снижает сложность проекта. Более высокая кривая обучения
    • Функция — это простейшая единица повторного использования кода, самая простая также означает более гибкую.. По сравнению с реквизитами компонентов передача параметров функций более гибкая; функции также легче комбинировать, хуки объединяют другие хуки или обычные функции для реализации сложной логики.
    • По сути, хуки — это концепция, которая передает состояние функции.
  • Компоненты более высокого порядка могут быть просто вложены и составлены, в то время как несколько хуков мозаичны, и могут быть определены более сложные отношения (зависимости)..

  • Более простое разделение логики и представленияХуки естественным образом изолируют JSX, а границы между представлениями и логикой становятся более четкими, что позволяет хукам больше фокусироваться на поведении компонентов.

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

  • Немного вкуса «реактивного программирования», каждый хук содержит некоторое состояние и побочные эффекты, которые данные могут передавать и реагировать между хуками, см. ниже

  • Повторное использование логики на разных платформах, Это моя собственная дыра в мозгу, после того как вышли React hooksЮ Юситолько что нажал одинvue-hooksПилотный проект, если разработка пойдет хорошо, можно ли использовать хуки для повторного использования между фреймворками?


ОдинПример: бесконечный список загрузки

Edit useList


Базовая структура кода общих хуков:

function useHook(options) {
  // ⚛️states
  const [someState, setSomeState] = useState(initialValue);
  // ⚛️derived state
  const computedState = useMemo(() => computed, [dependencies]);

  // ⚛️refs
  const refSomething = useRef();

  // ⚛️side effect
  useEffect(() => {}, []);
  useEffect(() => {}, [dependencies]);

  // ⚛️state operations
  const handleChange = useCallback(() => {
    setSomeState(newState)
  }, [])

  // ⚛️output
  return <div>{...}</div>
}

Структура кода пользовательских хуков и функциональных компонентов в основном одинакова, поэтому иногдаХуки пишутся и пишутся так, что чем больше похожи компоненты, тем больше похожи хуки компоненты написаны.Я думаю, можно считать, что компонент - это особый вид хуков, но он выводит Virtual DOM.


НемногоМеры предосторожности:

  • Хуки можно вызывать только на верхнем уровне компонента. Не вызывайте хуки в циклах, потоке управления и вложенных функциях
  • Хуки можно вызывать только из функциональных компонентов React.
  • Пользовательские хуки, названные с использованием *

Резюме крючковОбщие сценарии:

  • Инкапсуляция и мониторинг побочных эффектов: Например, useWindowSize (размер окна прослушивания), useOnlineStatus (статус в сети)
  • побочные эффекты: useEffect, useDebounce, useThrottle, useTitle, useSetTimeout
  • Инкапсуляция событий DOM: useActive, useFocus, useDraggable, useTouch
  • получить контекст
  • Инкапсулирует многократно используемую логику и состояние: useInput, usePromise (асинхронный запрос), useList (загрузка списка)
    • Замените компоненты более высокого порядка и реквизиты рендеринга. Например, useRouter заменяется на Router, а useSpring заменяет старый компонент Spring Render Props.
    • Замена компонентов контейнера
    • Диспетчер состояний: use-global-hook, не указано
  • Расширенные операции состояния: Исходный useState очень прост, поэтому есть много возможностей для расширения, таких как useSetState (имитирует старый setState), useToggle (переключение логических значений), useArray, useLocalStorage (синхронное сохранение в локальном хранилище).
  • Продолжайте мозговой штурм...: Изучение хуков еще не завершено.Продолжать

Изучите крючки:




5. Реализация хуков响应式программирование

VueнеинвазивныйОтзывчивая системаЭто одна из его самых уникальных функций. Он может управлять состоянием компонента в соответствии с привычками обработки данных Javascript, а затем автоматически реагировать на страницу. С другой стороны, React предоставляет setState. Для сложных состояний компонентов setState сделает код становится вонючим и длинным, например:

this.setState({
  pagination: {
    ...this.state.pagination,
    current: defaultPagination.current || 1,
    pageSize: defaultPagination.pageSize || 15,
    total: 0,
  },
});

Позже былоmobx, что в основном близко к опыту разработки Vue:

@observer
class TodoView extends React.Component {
  private @observable loading: boolean;
  private @observable error?: Error;
  private @observable list: Item[] = [];
  // 衍生状态
  private @computed get completed() {
    return this.list.filter(i => i.completed)
  }

  public componentDidMount() {
    this.load();
  }

  public render() {
    /// ...
  }

  private async load() {
    try {
      this.error = undefined
      this.loading = true
      const list = await fetchList()
      this.list = list
    } catch (err) {
      this.error = err
    } finally {
      this.loading = false
    }
  }
}

На самом деле у mobx тоже много недостатков:

  • Инвазивный код. Все компоненты, которые должны реагировать на изменения данных, должны быть украшены наблюдателем, свойства должны быть украшены наблюдаемыми и методами манипулирования данными. Связь с mobx глубокая, а стоимость переключения фреймворков или рефакторинга высока в будущее

  • Совместимость.Рефакторинг с использованием Proxy после mobx v5, Proxy поддерживается только после Chrome49.Если вы хотите быть совместимым со старыми браузерами, вы можете использовать только v4, v4 имеет некоторыеяма, новичкам, не знающим mobx, сложно найти эти ямки:

    • Наблюдаемые массивы не являются реальными массивами. Например, компонент таблицы antd не распознает массив mobx и должен быть преобразован между компонентами с использованием срезов.
    • Добавление свойств к существующему наблюдаемому объекту не захватывается автоматически

Так появились хуки, которые делают управление состоянием компонентов более простым и понятным, а его идея тоже очень близка к философии реактивного программирования mobx:

mobx


  1. Укажите кратко

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

function Demo() {
  const [list, setList] = useState<Item[]>([]);
  // ...
}
  1. производная

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

function Demo(props: { id: string }) {
  const { id } = props;
  // 取代mobx的observable: 获取列表, 在挂载或id变动时请求
  const [value, setValue, loading, error, retry] = usePromise(
    async id => {
      return getList(id);
    },
    [id],
  );

  // 衍生状态: 取代mobx的computed
  const unreads = useMemo(() => value.filter(i => !i.readed), [value]);

  // 衍生副作用: value变动后自动持久化
  useDebounce(
    () => {
      saveList(id, value);
    },
    1000,
    [value],
  );

  // 衍生视图
  return <List data={value} onChange={setValue} error={error} loading={loading} retry={retry} />;
}

Таким образом, хук — это революционная вещь, он может сделать поток данных о состоянии компонента более понятным.Вместо компонента класса наш обычный подход может быть вcomponentDidUpdateСравнение данных выполняется в методе жизненного цикла, а затем некоторые методы срабатывают императивно, например, getList срабатывает при изменении id, а saveList срабатывает при изменении списка.

Хук, кажется, разбавляет концепцию жизненного цикла компонента, позволяя разработчикам больше сосредоточиться на отношениях между состояниями и думать о разработке компонентов как о потоке данных.. Dan AbramovсуществуетНапишите устойчивые компонентыТакже упомянут принцип «не блокировать поток данных», подтверждающий идею автора:

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

Читатели могут посмотретьawesome-react-hooks, эти хуки с открытым исходным кодом довольно интересны, напримерrxjs-hooks, умело сочетает хуки реагирования и rxjs:

function App(props: { foo: number }) {
  // 响应props的变动
  const value = useObservable(inputs$ => inputs$.pipe(map(([val]) => val + 1)), 200, [props.foo]);
  return <h1>{value}</h1>;
}



6. Наследование классов также может быть полезным

Как сказано в официальной документации React: «Наш React использует тысячи компонентов, но мы не обнаружили ситуаций, в которых мы рекомендуем вам использовать наследование». React предпочитает композиционный паттерн функционального программирования, объектно-ориентированный. сценарии применения для наследования.

Когда нам нужно преобразовать некоторые традиционные сторонние библиотеки в библиотеки компонентов React, может пригодиться наследование.Поскольку большинство этих библиотек организованы с использованием объектно-ориентированной парадигмы, типичным из них является Map SDK.Карта БайдуНапример:

baidu overlay

Baidu Map имеет различные типы компонентов: элементы управления, оверлеи, tileLayers.Эти типы имеют несколько подклассов, как показано выше, оверлей имеет подклассы, такие как метка, маркер, полилиния и т. д., и эти подклассы имеют одинаковый жизненный цикл, все через метод addOverlay для рендеринга на холст карты Мы можем передать управление их жизненным циклом родительскому классу посредством наследования, например:

// Overlay抽象类, 负责管理Overlay的生命周期
export default abstract class Overlay<P> extends React.PureComponent<OverlayProps & P> {
  protected initialize?: () => void;
  // ...
  public componentDidMount() {
    // 子类在constructor或initialize方法中进行实例化
    if (this.initialize) {
      this.initialize();
    }

    if (this.instance && this.context) {
      // 渲染到Map画布中
      this.context.nativeInstance!.addOverlay(this.instance);
      // 初始化参数
      this.initialProperties();
    }
  }

  public componentDidUpdate(prevProps: P & OverlayProps) {
    // 属性更新
    this.updateProperties(prevProps);
  }

  public componentWillUnmount() {
    // 组件卸载
    if (this.instance && this.context) {
      this.context.nativeInstance!.removeOverlay(this.instance);
    }
  }
  // ...
  // 其他通用方法
  private forceReloadIfNeed(props: P, prevProps: P) {
    ...
  }
}

Работа подкласса становится намного проще, объявляя свои собственные свойства/события и создавая экземпляры конкретных классов:

export default class Label extends Overlay<LabelProps> {
  public static defaultProps = {
    enableMassClear: true,
  };

  public constructor(props: LabelProps) {
    super(props);
    const { position, content } = this.props;
    // 声明支持的属性和回调
    this.extendedProperties = PROPERTIES;
    this.extendedEnableableProperties = ENABLEABLE_PROPERTIES;
    this.extendedEvents = EVENTS;

    // 实例化具体类
    this.instance = new BMap.Label(content, {
      position,
    });
  }
}

код отreact-bdmap

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




7. Управление модальными ящиками

modal demo

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

const Demo: FC<{}> = props => {
  // ...
  const [visible, setVisible] = useState(false);
  const [editing, setEditing] = useState();
  const handleCancel = () => {
    setVisible(false);
  };

  const prepareEdit = async (item: Item) => {
    // 加载详情
    const detail = await loadingDeatil(item.id);
    setEditing(detail);
    setVisible(true);
  };

  const handleOk = async () => {
    try {
      const values = await form.validate();
      // 保存
      await save(editing.id, values);
      // 隐藏
      setVisible(false);
    } catch {}
  };

  return;
  <>
    <Table
      dataSource={list}
      columns={[
        {
          text: '操作',
          render: item => {
            return <a onClick={() => prepareEdit(item)}>编辑</a>;
          },
        },
      ]}
    />
    <Modal visible={visible} onOk={handleOk} onCancel={handleHide}>
      {/* 表单渲染 */}
    </Modal>
  </>;
};

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

const EditModal: FC<{ id?: string; visible: boolean; onCancel: () => void; onOk: () => void }> = props => {
  // ...
  const { visible, id, onHide, onOk } = props;
  const detail = usePromise(async (id: string) => {
    return loadDetail(id);
  });

  useEffect(() => {
    if (id != null) {
      detail.call(id);
    }
  }, [id]);

  const handleOk = () => {
    try {
      const values = await form.validate();
      // 保存
      await save(editing.id, values);
      onOk();
    } catch {}
  };

  return (
    <Modal visible={visible} onOk={onOk} onCancel={onCancel}>
      {detail.value &&
        {
          /* 表单渲染 */
        }}
    </Modal>
  );
};

/**
 * 使用
 */
const Demo: FC<{}> = props => {
  // ...
  const [visible, setVisible] = useState(false);
  const [editing, setEditing] = useState<string | undefined>(undefined);
  const handleHide = () => {
    setVisible(false);
  };

  const prepareEdit = async (item: Item) => {
    setEditing(item.id);
    setVisible(true);
  };

  return;
  <>
    <Table
      dataSource={list}
      columns={[
        {
          text: '操作',
          render: item => {
            return <a onClick={() => prepareEdit(item)}>编辑</a>;
          },
        },
      ]}
    />
    <EditModal id={editing} visible={visible} onOk={handleHide} onCancel={handleHide}>
      {' '}
    </EditModal>
  </>;
};

Теперь логика, связанная с редактированием, извлечена в EditModal, но компонент Demo также поддерживает открытое состояние модального окна и некоторые состояния данных. На сложной странице может быть много модальных блоков, и такой код будет становиться все более и более отвратительным, с различными состояниями xxxVisible, летящими по небу.С точки зрения практической разработки, самый простой способ управления модальными блоками должен быть таким:

const handleEdit = item => {
  EditModal.show({
    // 🔴 通过函数调用的方式出发弹窗. 这符合对模态框的习惯用法, 不关心模态框的可见状态. 例如window.confirm, wx.showModal().
    id: item.id, // 🔴 传递数据给模态框
    onOk: saved => {
      // 🔴 事件回调
      refreshList(saved);
    },
    onCancel: async () => {
      return confirm('确认取消'); // 控制模态框是否隐藏
    },
  });
};

Этот подход также вызывает споры в сообществе, некоторые считают его антипаттерном для React,@хочу еще трисуществуетНарушает ли Modal.confirm шаблон React?обсуждали этот вопрос. Возьмите картинку в качестве примера:

modal confirm
Картинка тоже из трехсменной статьи

Красная линия управляется временем (или синхронизацией), а синяя линия управляется данными. Ю Сангэн считает, что «даже проект React с очевидными функциями, управляемыми данными, имеет много частей, которые управляются не данными, а событиями. Данные могут управлять только состоянием, и только время может управлять поведением. вам не кажется странным, что вам нужно закрепить его в состоянии, управляемом данными?». Я не сужу, правильно ли его мнение, но некоторые сценарии строго «управляемы данными», и может быть много шаблонов. Код, трудно писать.

Итак, как добиться?

Вы можете обратиться кModal.confirmреализация, которая используетReactDOM.renderДля выполнения рендеринга плагинов некоторые люди также используютContext APIавтор считает, что идеалом (по крайней мере, с точки зрения API) являетсяreact-comfirmТакой:

/**
 * EditModal.tsx
 */
import { confirmable } from 'react-confirm';
const EditModal = props => {
  /*...*/
};

export default confirmable(EditModal);

/**
 *  Demo.tsx
 */
import EditModal from './EditModal';

const showEditModal = createConfirmation(EditModal);

const Demo: FC<{}> = props => {
  const prepareEdit = async (item: Item) => {
    showEditModal({
      id: item.id, // 🔴 传递数据给模态框
      onOk: saved => {
        // 🔴 事件回调
        refreshList(saved);
      },
      onCancel: async someValues => {
        return confirm('确认取消'); // 控制模态框是否隐藏
      },
    });
  };

  // ...
};

использоватьReactDOM.renderНедостаток формы рендеринга плагина в том, что он не может получить доступ к Context, поэтому необходимо скомпрометировать и реализовать пример в сочетании с Context API:

Edit useModal

расширять




8. Внедрение зависимостей с контекстом

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

Контекст очень часто используется в приложениях React.Context APIОн также очень прост в использовании. Context часто используется в следующих сценариях:

  • делиться данными, которые считаются «глобальными», с «деревом компонентов», текущий аутентифицированный пользователь, тема, конфигурация i18n, статус формы
  • Конфигурация компонента, Настройте поведение компонентов, таких как ConfigProvider antd.
  • Связь между компонентами, Общение через «события» не рекомендуется, но через «статус».
  • внедрение зависимости
  • государственный менеджерКонтекст может в основном заменить схемы управления состоянием Redux и Mobx после некоторой инкапсуляции, позже будут специальные статьи.

Область контекста — это поддерево, то есть поставщик контекста может быть применен к нескольким поддеревьям, и поставщик поддерева также может переопределить значение поставщика родительского элемента.Основная структура:

import React, {useState, useContext} from 'react'

export inteface MyContextValue {
  state: number
  setState: (state: number) => void
}

const MyContext = React.createContext<MyContextValue>(
  {
    state: 1,
    // 设置默认值, 抛出错误, 必须配合Provider使用
    setState: () => throw new Error('请求MyContextProvider组件下级调用')
  }
)

export const MyContextProvider: FC<{}> = props => {
  const [state, setState] = useState(1)
  return <MyContext.Provider value={{state, setState}}>{props.children}</MyContext.Provider>
}

export function useMyContext() {
  return useContext(MyContext)
}

export default MyContextProvider

Методы в контекстных значениях по умолчанию должны вызывать ошибки, предупреждая о нестандартном использовании.

Расширение:




9. Неизменяемое состояние

Неизменяемое состояние имеет важные последствия для React в парадигме функционального программирования.

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

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

    Таким образом, для диспетчера состояний (Redux), который строго требует однонаправленного потока данных, неизменяемые данные являются основным требованием. Он требует, чтобы все приложение отображалось одним состоянием. Неизменяемые данные могут сделать все приложение предсказуемым.

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

  • Рендеринг суждений может быть сделан точно. Сравнение ShouldComponentUpdate можно упростить.

Популярные способы реализации неизменяемых данных:

Автор предпочитает immer, здесь нет умственной нагрузки, а неизменяемые данные могут быть реализованы согласно методу работы объекта, к которому привык JS.




10. React-маршрутизатор: URL-адрес является состоянием

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

React-Router v4 — это настоящее соответствиесоставнойБиблиотека маршрутизации Thinking, React-Router официально называет ее «динамической маршрутизацией», а официальное объяснение таково: «имеется в виду маршрутизация, которая происходит при рендеринге приложения, а не маршрутизация, которая происходит в конфигурациях или соглашениях вне работающего приложения». В частности,<Route/>Он становится обычным компонентом React, который оценивает, соответствует ли URL-адрес URL-адресу при его рендеринге.Если он совпадает, он отображает указанный компонент, а если не соответствует, возвращает null.

В настоящее время значение URL отличается.URL больше не просто логотип страницы, а состояние приложения.; Состав приложения также больше не ограничивается плоскими страницами, а включает несколько областей, которые могут реагировать на статус URL (могут быть вложенными)., Поскольку мышление сильно изменилось, оно не было одобрено, когда оно впервые появилось. Этот метод более гибкий, поэтому выбор версии 4 не означает отказ от старого метода маршрутизации, вы можете следоватьпо старомуреализовать маршрутизацию страниц.

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

router demo

Сначала создайте URL-адрес, который может выражать эту каскадную связь, например/{group}/{id}, дизайн URL обычно соответствуетОСТАЛЬНЫЙ стиль, то примерная структура приложения выглядит следующим образом:

// App
const App = () => {
  <div className="app">
    <SideBar />
    <Route path="/:group" component={ListPage} />
    <Route path="/:group/:id" component={Detail} />
  </div>;
};

// SideBar
const Sidebar = () => {
  return (
    <div className="sidebar">
      {/* 使用NavLink 在匹配时显示激活状态 */}
      <NavLink to="/message">消息</NavLink>
      <NavLink to="/task">任务</NavLink>
      <NavLink to="/location">定位</NavLink>
    </div>
  );
};

// ListPage
const ListPage = props => {
  const { group } = props.match.params;
  // ...

  // 响应group变化, 并加载指定类型列表
  useEffect(() => {
    load(group);
  }, [group]);

  // 列表项也会使用NavLink, 用于匹配当前展示的详情, 激活显示
  return <div className="list">{renderList()}</div>;
};

// DetailPage
const DetailPage = props => {
  const { group, id } = props.match.params;
  // ...

  // 响应group和id, 并加载详情
  useEffect(() => {
    loadDetail(group, id);
  }, [group, id]);

  return <div className="detail">{renderDetail()}</div>;
};

расширять




11. Спецификации компонентов

  • Включить строгий режим. Включите строгий режим, чтобы обнаруживать потенциальные проблемы и нерегулярное использование как можно раньше.
  • Спецификации сторонней разработки:

расширять