Вы действительно понимаете жизненный цикл React?
Жизненный цикл React известен многим, но обычно все, что мы знаем, этоодин компонентжизненный цикл, но дляКомпонент хуков, несколько связанных компонентовКаков жизненный цикл (родительско-дочерние компоненты и одноуровневые компоненты)? Вы думали об этом и поняли?Далее у нас будет полное представление о жизненном цикле React.
окомпоненты, мы имеем в виду здесьReact.Component
так же какReact.PureComponent
, но включает ли он компонент Hooks?
1. Компонент крючков
функциональный компонентСуть - функция, понятия состояния нет, поэтомунет жизненного циклаОдним словом, простофункция рендерингаВот и все.
но импортироватьHooksТогда он становится другим, он позволяет компонентам использовать состояние и другие возможности React без использования классов, и он ближе к реализации синхронизации состояния, чем к жизненному циклу класса, чем к жизненному циклу события ответа. но мы можем использоватьuseState
,useEffect()
а такжеuseLayoutEffect()
смоделировать жизненный цикл реализации.
который:Компоненты хуков ближе к реализации синхронизации состояния, чем к реагированию на события жизненного цикла..
Ниже приведен конкретный жизненный цикл и хукиПереписка:
-
constructor
: Функциональные компоненты не нуждаются в конструкторе, мы можем сделать это, вызвавuseState
для инициализации состояния. Если вычисления дорогие, вы также можете передать функцию вuseState
.const [num, UpdateNum] = useState(0)
-
getDerivedStateFromProps
: В общем, нам не нужно его использовать, мы можемОбновление состояния во время рендеринга, чтобы достичьgetDerivedStateFromProps
цель.function ScrollView({row}) { let [isScrollingDown, setIsScrollingDown] = useState(false); let [prevRow, setPrevRow] = useState(null); if (row !== prevRow) { // Row 自上次渲染以来发生过改变。更新 isScrollingDown。 setIsScrollingDown(prevRow !== null && row > prevRow); setPrevRow(row); } return `Scrolling down: ${isScrollingDown}`; }
React немедленно завершит первый рендеринг и повторно запустит компонент с обновленным состоянием, чтобы избежать слишком больших затрат производительности.
-
shouldComponentUpdate
:Можно использоватьReact.memo
обернуть компонент в егоprops
проводить поверхностные сравненияconst Button = React.memo((props) => { // 具体的组件 });
Уведомление:
React.memo
равноPureComponent
, который только поверхностно сравнивает реквизит. здесь тоже можно использоватьuseMemo
Оптимизируйте каждый узел. -
render
: это само тело функционального компонента. -
componentDidMount
,componentDidUpdate
:useLayoutEffect
Фаза вызова такая же, как и у них обоих. Тем не менее, мы рекомендуем вамСначала используйте useEffect, и пытайтесь использовать его только в случае неудачиuseLayoutEffect
.useEffect
Комбинация всего этого может быть выражена.// componentDidMount useEffect(()=>{ // 需要在 componentDidMount 执行的内容 }, []) useEffect(() => { // 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容 document.title = `You clicked ${count} times`; return () => { // 需要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵守先清理后更新) // 以及 componentWillUnmount 执行的内容 } // 当函数中 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关 }, [count]); // 仅在 count 更改时更新
Помните, что React будет ждать, пока браузер завершит отрисовку экрана, прежде чем отложить вызов.
useEffect
, что делает дополнительные операции удобными -
componentWillUnmount
: эквивалентноuseEffect
вернулся внутрьcleanup
функция// componentDidMount/componentWillUnmount useEffect(()=>{ // 需要在 componentDidMount 执行的内容 return function cleanup() { // 需要在 componentWillUnmount 执行的内容 } }, [])
-
componentDidCatch
andgetDerivedStateFromError
:В настоящее времяЕще нетЭквиваленты хуков для этих методов, но скоро будут добавлены.
Для удобства памяти они примерно сведены в таблицу следующим образом.
компонент класса | Крючки |
---|---|
constructor | useState |
getDerivedStateFromProps | функция обновления в useState |
shouldComponentUpdate | useMemo |
render | сама функция |
componentDidMount | useEffect |
componentDidUpdate | useEffect |
componentWillUnmount | Функция, возвращаемая в useEffect |
componentDidCatch | без |
getDerivedStateFromError | без |
Во-вторых, жизненный цикл одного компонента
1. Жизненный цикл
До версии 16.3
Мы можем разделить жизненный цикл на три фазы:
- горная сцена
- Этап обновления компонента
- этап удаления
Отдельно:
- горная сцена
-
constructor
: Избегайте копирования значения реквизита в состояние componentWillMount
-
render
: Самый важный шаг реакции, создание виртуального дома, выполнение алгоритма сравнения и обновление дерева домов выполняются здесь. componentDidMount
-
- Этап обновления компонента
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
- этап удаления
componentWillUnMount
Есть проблема с этим жизненным циклом, то есть при обновлении компонентов верхнего уровня сложных компонентов стек вызовов будет очень длинным, при выполнении сложных операций основной поток может быть заблокирован на длительное время, в результате чего плохой пользовательский опыт. ,FiberОн родился, чтобы решить эту проблему.
После версии 16.3
Fiber — это, по сути, кадр виртуального стека, и новый планировщик будет свободно планировать эти кадры в соответствии с приоритетом, тем самым изменяя предыдущий синхронный рендеринг на асинхронный рендеринг и вычисляя обновления в сегментах, не влияя на работу.
Для асинхронного рендеринга есть две фазы:
-
reconciliation
:componentWillMount
componentWillReceiveProps
shouldConmponentUpdate
componentWillUpdate
-
commit
componentDidMount
componentDidUpdate
в,reconciliation
Фазы могут быть прерваны, поэтомуreconcilation
Функция, выполняемая на этапе, будет вызываться несколько раз, очевидно, что это неразумно.
Итак, V16.3 представил новый API для решения этой проблемы:
-
static getDerivedStateFromProps
: Функция находится вЭтап монтирования и этап обновления компонентовбудет выполнено, т.е.Каждый раз получайте новыйprops
илиstate
будет выполнен после,вместо этого используется на этапе монтированияcomponentWillMount
; сотрудничать на этапе обновления компонентаcomponentDidUpdate
, может покрытьcomponentWillReceiveProps
все виды использования .В то же время это статическая функция, поэтому доступ к телу функции невозможен.
this
, будет основываться наnextProps
а такжеprevState
Рассчитывается ожидаемое изменение состояния, и возвращенный результат отправляется вsetState
,вернутьnull
это означает, что обновление не требуетсяstate
, и этот возвратнеобходимо. -
getSnapshotBeforeUpdate
: функция будет вrender
После, до обновления DOMВызывается для чтения последних данных DOM.возвращает значение,так как
componentDidUpdate
третий параметр;сотрудничатьcomponentDidUpdate
, может покрытьcomponentWillUpdate
все виды использования .
Примечание. V16.3 используется только для монтажа компонентов или компонентов.props
Будет вызван процесс обновления, то есть если он инициирован собственным setState или forceUpdate, а не родительским компонентом, тоstatic getDerivedStateFromProps
тоже не будет вызываться, исправлено на вызов обоих в V16.4.
То есть обновленный жизненный цикл это:
- горная сцена
constructor
static getDerivedStateFromProps
render
componentDidMount
- фаза обновления
static getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
- этап удаления
componentWillUnmount
2. Жизненный цикл, непонимание
Непонимание одно:getDerivedStateFromProps
а также componentWillReceiveProps
только вprops
Изменятьбудет вызван, когда
Фактически,Как только родитель перерендерится,getDerivedStateFromProps
а также componentWillReceiveProps
будет вызван снова, независимо отprops
Были ли какие-либо изменения. Поэтому небезопасно напрямую назначать реквизиты для состояния в этих двух методах.
// 子组件
class PhoneInput extends Component {
state = { phone: this.props.phone };
handleChange = e => {
this.setState({ phone: e.target.value });
};
render() {
const { phone } = this.state;
return <input onChange={this.handleChange} value={phone} />;
}
componentWillReceiveProps(nextProps) {
// 不要这样做。
// 这会覆盖掉之前所有的组件内 state 更新!
this.setState({ phone: nextProps.phone });
}
}
// 父组件
class App extends Component {
constructor() {
super();
this.state = {
count: 0
};
}
componentDidMount() {
// 使用了 setInterval,
// 每秒钟都会更新一下 state.count
// 这将导致 App 每秒钟重新渲染一次
this.interval = setInterval(
() =>
this.setState(prevState => ({
count: prevState.count + 1
})),
1000
);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<>
<p>
Start editing to see some magic happen :)
</p>
<PhoneInput phone='call me!' />
<p>
This component will re-render every second. Each time it renders, the
text you type will be reset. This illustrates a derived state
anti-pattern.
</p>
</>
);
}
}
Конечно, мы можем сделать это в приложении родительского компонента.shouldComponentUpdate
Сравните, изменено ли электронное письмо реквизитов, а затем решите, нужно ли повторно отображать, но если дочерний компонент принимает несколько реквизитов (более сложный), это трудно обработать, иshouldComponentUpdate
Он в основном используется для повышения производительности, и разработчикам не рекомендуется использоватьshouldComponetUpdate
(можно использоватьReact.PureComponet
).
Мы также можем использоватьИзменить состояние после изменения реквизита.
class PhoneInput extends Component {
state = {
phone: this.props.phone
};
componentWillReceiveProps(nextProps) {
// 只要 props.phone 改变,就改变 state
if (nextProps.phone !== this.props.phone) {
this.setState({
phone: nextProps.phone
});
}
}
// ...
}
Но это также приведет к проблеме: когда реквизиты более сложные, отношения между реквизитами и состоянием нелегко контролировать, что может привести к проблемам.
Решение первое:Полностью управляемые компоненты
function PhoneInput(props) {
return <input onChange={props.onChange} value={props.phone} />;
}
Полностью контролируется реквизитами, состояние не выводится
Решение второе:Неуправляемые компоненты с ключами
class PhoneInput extends Component {
state = { phone: this.props.defaultPhone };
handleChange = event => {
this.setState({ phone: event.target.value });
};
render() {
return <input onChange={this.handleChange} value={this.state.phone} />;
}
}
<PhoneInput
defaultPhone={this.props.user.phone}
key={this.props.user.id}
/>
когда key
Когда он изменится, ReactСоздайте новый компонент вместо обновления существующего
Недоразумение 2: скопировать значение реквизита непосредственно в состояние
Избегайте копирования значения реквизита в состояние
constructor(props) {
super(props);
// 千万不要这样做
// 直接用 props,保证单一数据源
this.state = { phone: props.phone };
}
3. Порядок выполнения нескольких компонентов
1. Родительско-дочерние компоненты
-
горная сцена
Минутадвасцена:
- первыйодинФаза, от родительского компонента к собственному
render
, проанализируйте, какие подкомпоненты нужно отрисовывать под него, и проанализируйте, какие подкомпонентыСинхронизированные дочерние компонентычтобы создать, нажмитерекурсивный порядокВыполняйте каждый подкомпонент один за другим, чтобыrender
, создайте дерево Virtual DOM, соответствующее родительскому и дочернему компонентам, и зафиксируйте его в DOM. - первыйдваНа этом этапе DOM-узел сгенерирован, монтирование компонента завершено и начинается последующий процесс. Сначала запускайте соответствующие подкомпоненты синхронизации по очереди.
componentDidMount
, который, наконец, запускает родительский компонент.
Уведомление: если родительский компонент содержит асинхронный дочерний компонент, он будет создан после монтирования родительского компонента.
Итак, порядок выполнения такой:
Родительский компонент getDerivedStateFromProps —> синхронный дочерний компонент getDerivedStateFromProps —> синхронный дочерний компонент componentDidMount —> родительский компонент componentDidMount —> асинхронный дочерний компонент getDerivedStateFromProps —> асинхронный дочерний компонент componentDidMount
- первыйодинФаза, от родительского компонента к собственному
-
фаза обновления
React разработан, чтобы следовать модели одностороннего потока данных., то есть данные передаются от родительского компонента к дочернему компоненту.
-
первыйодинфаза, которая начинается с родительского компонента, выполняется
static getDerivedStateFromProps
shouldComponentUpdate
обновить до собственного
render
, проанализировать, какие подкомпоненты нужно отрисовывать под него, иПодсборкачтобы создать, нажмитерекурсивный порядокВыполняйте каждый подкомпонент один за другим, чтобыrender
, создайте дерево Virtual DOM, соответствующее родительскому и дочернему компонентам, и сравните его с существующим деревом Virtual DOM, чтобы вычислитьЧасть виртуального DOM, которая действительно меняется, и только собственные манипуляции с DOM для этой части. -
первыйдваНа этом этапе DOM-узел сгенерирован, монтирование компонента завершено и начинается последующий процесс. Сначала поочередно запускайте следующие функции синхронизированных дочерних компонентов и, наконец, запускайте родительский компонент.
getSnapshotBeforeUpdate()
componentDidUpdate()
React будет выполнять эти функции в указанном выше порядке, каждая функция выполняется сначала для каждого дочернего компонента, а затем выполняется родительский компонент.
Итак, порядок выполнения такой:
Родительский компонент getDerivedStateFromProps —> родительский компонент shouldComponentUpdate —> дочерний компонент getDerivedStateFromProps —> дочерний компонент shouldComponentUpdate —> дочерний компонент getSnapshotBeforeUpdate —> родительский компонент getSnapshotBeforeUpdate —> дочерний компонент componentDidUpdate —> родительский компонент componentDidUpdate
-
-
этап удаления
componentWillUnmount()
, в порядкеРодительский компонент выполняется первым, а дочерние компоненты выполняют свои соответствующие методы в порядке, определенном в JSX..Уведомление: Если удаление старого компонента сопровождается созданием нового компонента, то новый компонент будет создан и выполнен первым.
render
, затем выгружайте старые компоненты, которые не нужны, и, наконец, новый компонент выполняет обратный вызов завершения монтирования.
2. Компоненты Brother
-
горная сцена
В случае синхронных маршрутов порядок, в котором они создаются, и порядок, в котором они определяются в общем родительском компоненте,последовательныйиз.
В случае асинхронных маршрутов порядок их создания совпадает с порядком загрузки js.
-
этап обновления, этап удаления
Связь между одноуровневыми узлами осуществляется в основном через родительский компонент (Redux и Context также передаются вниз путем изменения родительского компонента).
props
осуществленный),Дизайн, удовлетворяющий требованиям React, следует модели одностороннего потока данных.,Таким образом, связь между любыми двумя компонентами можно по существу отнести к обновлению родительского и дочернего компонентов..Поэтому для получения сведений об обновлении и удалении компонентов Brother см.компонент родитель-потомок.
В заключение: я рекомендую онлайн-инструмент для редактирования:StackBlitz, может редактировать проекты Angular, React, TypeScript, RxJS, Ionic, Svelte онлайн
Примечание: Жизненный цикл высокоуровневых компонентов будет добавлен в будущем, так что следите за обновлениями маленькой бутылочки.
серия статей
Хотите увидеть больше статей в серии,Нажмите, чтобы перейти на домашнюю страницу блога github.
Прогулка в конце, добро пожаловать, чтобы обратить внимание: джентльмен переднего конца бутылки, ежедневное обновление