(Часть 2) Коды интервью для производителей интерфейсов среднего и продвинутого уровня

внешний интерфейс опрос
(Часть 2) Коды интервью для производителей интерфейсов среднего и продвинутого уровня

Большое спасибо за все проблемы, которые вы указали в комментариях. Из-за большого количества изменений в прошлом году он был отложен, а проблемы и предложения, на которые указали большие ребята, были исправлены. Пожалуйста, будьте уверены, чтобы поесть! Спасибо~🥳

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

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

введение

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

Средняя часть в основном описывает React со следующих аспектов:

Изначально планировалось, что будет всего две главы, но чем больше я писала, тем больше писала.Из-за ограничения места и для лучшего восприятия чтения пришлось разделить среднюю часть.Надеюсь вы не против. 🙃, Кроме того, в следующей части также есть знания по гибридному приложению/веб-пакету/оптимизации производительности/Nginx, так что следите за обновлениями.

Рекомендуется начать с основ предыдущей статьи~ Существует пошаговый процесс:

Блог Xiaocaiji выпрашивает лайки 🙂blog

продвинутые знания

Фреймворк: Реагировать

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

1. Fiber

Основной процесс React можно разделить на две части:

  • reconciliation (Алгоритм планирования, также известный как визуализация):
    • Обновление состояния и реквизита;
    • Вызов хуков жизненного цикла;
    • создать виртуальный дом;
      • Здесь больше подходит название Fiber Tree;
    • Алгоритм diff выполняется через старый и новый vdom для получения изменения vdom;
    • Определите, требуется ли повторный рендеринг
  • commit:
    • При необходимости выполните обновление узла dom;

Чтобы понять Fiber, давайте сначала посмотрим, зачем он нам нужен?

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

  • решение: Обычно существует два способа решения блокировки синхронизации:асинхронныйа такжесегментация задач. React Fiber был создан для сегментации задач.

  • Кратко:

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

    • Волокно здесь можно представить какструктура данных:
    class Fiber {
    	constructor(instance) {
    		this.instance = instance
    		// 指向第一个 child 节点
    		this.child = child
    		// 指向父节点
    		this.return = parent
    		// 指向第一个兄弟节点
    		this.sibling = previous
    	}	
    }
    
    • алгоритм обхода дерева связанных списков: пройти черезСохранение и сопоставление узлов, он может останавливаться и перезапускаться в любое время, чтобы достичь основной предпосылки разделения задач;

      • 1. Во-первых, путем непрерывного обхода дочерних узлов до конца дерева;
      • 2. Начать обход одноуровневых узлов через одноуровневых;
      • 3. return возвращается к родительскому узлу и продолжает выполнение 2;
      • 4. После корневого узла выпрыгнуть из обхода;
    • сегментация задач, обновление рендеринга в React можно разделить на две фазы:

      • этап примирения: Сравнение данных vdom является подходящим этапом для разделения.Например, после сравнения части дерева сначала приостановить выполнение вызова анимации, а затем вернуться, чтобы продолжить сравнение после завершения.
      • Стадия фиксации: Обновите список изменений до dom, который не подходит для разделения, чтобы синхронизировать данные и пользовательский интерфейс. В противном случае обновление данных может не соответствовать пользовательскому интерфейсу из-за блокировки обновления пользовательского интерфейса.
    • Децентрализованное исполнение: после того, как задача разделена, небольшие блоки задач могут быть распределены на период простоя браузера для постановки в очередь на выполнение, а ключом к реализации являются два новых API:requestIdleCallbackа такжеrequestAnimationFrame

      • задачи с низким приоритетомrequestIdleCallbackОбработка, это функция обратного вызова для периода простоя цикла обработки событий, предоставляемого браузером, требует выполнения поллифилла и имеет параметр крайнего срока для ограничения выполнения событий для продолжения разделения задач;
      • задачи с высоким приоритетомrequestAnimationFrameиметь дело с;
    // 类似于这样的方式
    requestIdleCallback((deadline) => {
        // 当有空闲时间时,我们执行一个组件渲染;
        // 把任务塞到一个个碎片时间中去;
        while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextComponent) {
            nextComponent = performWork(nextComponent);
        }
    });
    
    • приоритетная стратегия: ввод текстового поля > Задачи, которые необходимо выполнить в конце этого расписания > Анимационный переход > Интерактивная обратная связь > Обновление данных > Задачи, которые не будут отображаться, но будут отображаться в будущем

Tips:

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

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

Спасибо @Pengyuan Children's Shoes за указание на несколько основных концепций Fiber в комментариях, спасибо! !

2. Жизненный цикл

В новой версии у React официально появился новый жизненный циклИзменить предложение:

  • использоватьgetDerivedStateFromPropsзаменятьcomponentWillMountа такжеcomponentWillReceiveProps;
  • использоватьgetSnapshotBeforeUpdateзаменятьcomponentWillUpdate;
  • избегать использованияcomponentWillReceiveProps;

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

  • reconciliation:

    • componentWillMount
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
  • commit:

    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount

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

Предлагаемый жизненный цикл новой версии выглядит следующим образом:

class Component extends React.Component {
  // 替换 `componentWillReceiveProps` ,
  // 初始化和 update 时被调用
  // 静态函数,无法使用 this
  static getDerivedStateFromProps(nextProps, prevState) {}
  
  // 判断是否需要更新组件
  // 可以用于组件性能优化
  shouldComponentUpdate(nextProps, nextState) {}
  
  // 组件被挂载后触发
  componentDidMount() {}
  
  // 替换 componentWillUpdate
  // 可以在更新之前获取最新 dom 数据
  getSnapshotBeforeUpdate() {}
  
  // 组件更新后调用
  componentDidUpdate() {}
  
  // 组件即将销毁
  componentWillUnmount() {}
  
  // 组件已销毁
  componentDidUnmount() {}
}
  • Рекомендации:

    • существуетconstructorинициализировать состояние;
    • существуетcomponentDidMountмониторинг событий вcomponentWillUnmountсобытие отвязки;
    • существуетcomponentDidMountзапрос данных вcomponentWillMount;
    • Когда вам нужно обновить состояние на основе реквизита, используйтеgetDerivedStateFromProps(nextProps, prevState);
      • Старый реквизит нужно хранить отдельно для сравнения;
    public static getDerivedStateFromProps(nextProps, prevState) {
    	// 当新 props 中的 data 发生变化时,同步更新到 state 上
    	if (nextProps.data !== prevState.data) {
    		return {
    			data: nextProps.data
    		}
    	} else {
    		return null1
    	}
    }
    
    • допустимыйcomponentDidUpdateПрослушивайте изменения свойств или состояния, например:
    componentDidUpdate(prevProps) {
    	// 当 id 发生变化时,重新获取数据
    	if (this.props.id !== prevProps.id) {
    		this.fetchData(this.props.id);
    	}
    }
    
    • существуетcomponentDidUpdateиспользоватьsetStateПри необходимо добавить условие, иначе оно войдет в бесконечный цикл;
    • getSnapshotBeforeUpdate(prevProps, prevState)Последние данные рендеринга можно получить перед обновлением, оно вызывается после рендера и перед обновлением;
    • shouldComponentUpdate: по умолчанию каждый вызовsetState, обязательно попадет на стадию сравнения, но может бытьshouldComponentUpdateКрюк жизни возвращаетсяfalseЧтобы напрямую предотвратить выполнение последующей логики, обычно используется условный рендеринг для оптимизации производительности рендеринга.

3. setState

в пониманииsetStateПрежде давайте кратко разберемся со структурой упаковки React:Transaction:

  • дела (Transaction):
    • это структура вызова в React, используемая для обертывания метода, структура такова:initialize - perform(method) - close. Через транзакцию можно единообразно управлять началом и концом метода, в потоке транзакций это означает, что процесс выполняет какие-то операции;

  • setState: используется в React для изменения состояния и обновления представления. Он имеет следующие характеристики:

  • Асинхронный и синхронный: setStateОн не является чисто асинхронным или синхронным, он фактически связан с окружением во время вызова:

    • существуетсинтетическое событиеа такжеКрючки жизненного цикла (кроме componentDidUpdate)середина,setStateявляется «асинхронным»;
      • причина: Потому чтоsetStateВ реализации есть суждение: когда стратегия обновления находится в выполнении потока транзакций, обновление компонента будет помещено вdirtyComponentsОжидание выполнения в очереди, в противном случае начать выполнениеbatchedUpdatesобновление очереди;
        • В вызове ловушки жизненного цикла стратегия обновления предшествует обновлению, компонент все еще находится в потоке транзакций иcomponentDidUpdateПосле обновления компонент больше не находится в потоке транзакций, поэтому он будет выполняться синхронно;
        • В синтетических событиях React основан наМеханизм делегирования событий для завершения потока транзакцийРеализация также находится в потоке транзакций;
      • вопрос: недоступноsetStateнезамедлительно послеthis.stateчтобы получить обновленное значение.
      • решать: если вам нужно немедленно синхронизироваться, чтобы получить новые значения,setStateФактически, второй параметр может быть передан.setState(updater, callback), самое последнее значение можно получить в обратном вызове;
    • существуетродное событиеа такжеsetTimeoutсередина,setStateЭто синхронно, и обновленное значение может быть получено немедленно;
      • Причина: родное событие является реализацией самого браузера, не имеет ничего общего с потоком транзакций и, естественно, является синхронным;setTimeoutПомещается в поток таймера для отложенного выполнения, в это время поток транзакций закончился, поэтому тоже синхронизируется;
  • Массовое обновление: существуетсинтетическое событиеа такжекрючки жизненного цикласередина,setStateПри обновлении очереди сохраняется то, чтостатус слияния(Object.assign). Поэтому установленное ранее значение ключа позже будет перезаписано, и в итоге будет выполнено только одно обновление;

  • функциональный: из-за проблем с волокном и слиянием официальные рекомендации могут быть переданы вфункцияформа.setState(fn),существуетfnвернуть новый вstateобъект, напр.this.setState((state, props) => newState);

    • Используя функциональный стиль, можно избежатьsetStateЛогика пакетного обновления, входящая функция будетпоследовательный вызов;
  • Меры предосторожности:

    • setState объединяется, и несколько последовательных вызовов в синтетических событиях и ловушках жизненного цикла будут оптимизированы в один;
    • Когда компонент был уничтожен, при повторном вызовеsetState, React сообщит об ошибке, обычно есть два решения:
      • Смонтируйте данные извне и передайте через пропсы, например, в Redux или родителях;
      • Поддерживать количество состояний внутри компонента (isUnmounted),componentWillUnmountотмечен как истинный вsetStateрешение перед

4. HOC (компонент высшего порядка)

HOC (Higher Order Component) — это компонентная модель, сформированная сообществом в рамках механизма React, который является мощным во многих сторонних библиотеках с открытым исходным кодом.

  • Кратко:

    • Компоненты более высокого порядка не являются компонентами, ониФункция улучшения, вы можете ввести метакомпонент и вернуть новый расширенный компонент;
    • Основная роль компонентов высшего порядка состоит в том, чтобыповторное использование кода,действоватьстатус и параметры;
  • Применение:

    • Реквизит Прокси: возвращает компонент, основанный на обернутом компонентеулучшение функции;

      • параметры по умолчанию: Вы можете обернуть слой параметров по умолчанию для компонента;
      function proxyHoc(Comp) {
      	return class extends React.Component {
      		render() {
      			const newProps = {
      				name: 'tayde',
      				age: 1,
      			}
      			return <Comp {...this.props} {...newProps} />
      		}
      	}
      }
      
      • Извлечь состояние: Состояние в обернутом компоненте может зависеть от внешнего слоя через свойства, например, для компонентов, контролируемых переходом:
      function withOnChange(Comp) {
      	return class extends React.Component {
      		constructor(props) {
      			super(props)
      			this.state = {
      				name: '',
      			}
      		}
      		onChangeName = () => {
      			this.setState({
      				name: 'dongdong',
      			})
      		}
      		render() {
      			const newProps = {
      				value: this.state.name,
      				onChange: this.onChangeName,
      			}
      			return <Comp {...this.props} {...newProps} />
      		}
      	}
      }
      

      Поза следующая, так что очень быстроInputКомпоненты преобразуются в управляемые компоненты.

      const NameInput = props => (<input name="name" {...props} />)
      export default withOnChange(NameInput)
      
      • компоненты упаковки: для обернутого элемента может быть выполнен слой обертывания,
      function withMask(Comp) {
        return class extends React.Component {
            render() {
      		  return (
      		      <div>
      				  <Comp {...this.props} />
      					<div style={{
      					  width: '100%',
      					  height: '100%',
      					  backgroundColor: 'rgba(0, 0, 0, .6)',
      				  }} 
      			  </div>
      		  )
      	  }
        }
      }
      
    • обратное наследование(Инверсия наследования): возвращает компонент,Унаследовано от обернутого компонента, обычно используется для следующих операций:

      function IIHoc(Comp) {
          return class extends Comp {
              render() {
                  return super.render();
              }
          };
      }
      
      • рендеринг угона (Render Highjacking)

        • условный рендеринг: Рендеринг различных компонентов в зависимости от условий
        function withLoading(Comp) {
            return class extends Comp {
                render() {
                    if(this.props.isLoading) {
                        return <Loading />
                    } else {
                        return super.render()
                    }
                }
            };
        }
        
        • Вы можете напрямую изменить дерево элементов React, отображаемое обернутым компонентом.
      • рабочее состояние(Рабочее состояние): вы можете напрямую пройтиthis.stateПолучите состояние обернутого компонента и работайте с ним. Но такая операция может легко сделать состояние трудным для отслеживания, поддержки и использования с осторожностью.

  • Сценарии применения:

    • Контроль доступа, с помощью абстрактной логики единообразно оценивать разрешения страницы и отображать страницу в соответствии с различными условиями:
    function withAdminAuth(WrappedComponent) {
        return class extends React.Component {
    		constructor(props){
    			super(props)
    			this.state = {
    		    	isAdmin: false,
    			}
    		} 
    		async componentWillMount() {
    		    const currentRole = await getCurrentUserRole();
    		    this.setState({
    		        isAdmin: currentRole === 'Admin',
    		    });
    		}
    		render() {
    		    if (this.state.isAdmin) {
    		        return <Comp {...this.props} />;
    		    } else {
    		        return (<div>您没有权限查看该页面,请联系管理员!</div>);
    		    }
    		}
        };
    }
    
    • мониторинг производительности, обернуть жизненный цикл компонента и выполнить унифицированное захоронение:
    function withTiming(Comp) {
        return class extends Comp {
            constructor(props) {
                super(props);
                this.start = Date.now();
                this.end = 0;
            }
            componentDidMount() {
                super.componentDidMount && super.componentDidMount();
                this.end = Date.now();
                console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);
            }
            render() {
                return super.render();
            }
        };
    }
    
    • повторное использование кода, повторяющуюся логику можно абстрагировать.
  • Используйте Примечание:

      1. чистая функция: Функция улучшения должна быть чистой функцией, чтобы избежать вторжения и модификации метакомпонентов;
      1. Избегайте загрязнения окружающей среды: В идеале несвязанные параметры и события метакомпонента должны передаваться прозрачно, а использование должно оставаться неизменным, насколько это возможно;
      1. Пространства имен: добавить конкретное имя компонента в HOC, что может облегчить разработку и отладку, а также поиск проблем;
      1. пройти по ссылке: Если вам нужно передать рефы метакомпонента, вы можете использоватьReact.forwardRef;
      1. статический метод: статический метод метакомпонента не может быть передан автоматически, что приведет к сбою вызова бизнес-уровня; решение:
      • экспорт функции
      • назначение статического метода
      1. перерисовать: Поскольку функция улучшения возвращает новый компонент каждый раз, когда она вызывается, если функция улучшения используется в Render, весь HOC будет каждый раз перерисовываться, а предыдущее состояние будет потеряно;

5. Redux

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

  • основная идея:

    • единственный источник правды: все приложение имеет только одно дерево состояний, то есть все состояния в конечном итоге хранятся в хранилище корневого уровня;
    • Статус только для чтения: Чтобы обеспечить управляемость состояния, лучше всего следить за изменениями состояния. Тогда есть два необходимых условия:
      • Данные в Redux Store нельзя изменить напрямую;
      • Строго контролировать выполнение модификаций;
    • чистая функция: указывает, что модификации могут быть описаны только чистой функцией (Reducer);
  • Приблизительная структура данных выглядит следующим образом:

  • Реализация концепции:

    • Store: синглтон Global Store, под каждым приложением Redux есть только один магазин, у него есть следующие методы для использования:
      • getState: получить состояние;
      • dispatch: триггерное действие, обновление состояния;
      • subscribe: подписываться на изменения данных, регистрировать слушателей;
    // 创建
    const store = createStore(Reducer, initStore)
    
    • Action: он используется как носитель поведения для отображения соответствующего Reducer, и он может стать носителем данных, передавая данные из приложения в хранилище, которое является хранилищем.единственный источник данных;
    // 一个普通的 Action
    const action = {
    	type: 'ADD_LIST',
    	item: 'list-item-1',
    }
    
    // 使用:
    store.dispatch(action)
    
    // 通常为了便于调用,会有一个 Action 创建函数 (action creater)
    funtion addList(item) {
    	return const action = {
    		type: 'ADD_LIST',
    		item,
    	}
    }
    
    // 调用就会变成:
    dispatch(addList('list-item-1'))
    
    • Reducer: чистая функция, используемая для описания того, как изменять данные, Action — это имя поведения, а Reducer — суть изменения поведения;
    // 一个常规的 Reducer
    // @param {state}: 旧数据
    // @param {action}: Action 对象
    // @returns {any}: 新数据
    const initList = []
    function ListReducer(state = initList, action) {
    	switch (action.type) {
    		case 'ADD_LIST':
    			return state.concat([action.item])
    			break
    		defalut:
    			return state
    	}
    }
    

    Уведомление:

    1. Соблюдайте неизменность данных, не изменяйте состояние напрямую, а возвращайтеновый объект,можно использоватьassign / copy / extend / 解构и т.д. для создания новых объектов;
    2. Требуется по умолчаниювернуть исходные данные, чтобы избежать очистки данных;
    3. лучшая обстановкаПервоначальный значение, что удобно для инициализации приложения и стабильности данных;
  • Передовой:

    • React-Redux: Используйте с React;
      • <Provider>: передать хранилище в компонент через контекст;
      • connect: компонент более высокого порядка, упрощающий использование Redux в компонентах React;
          1. будетstoreпройти черезmapStateToPropsиспользовать после фильтрацииpropsвводить компоненты
          1. согласно сmapDispatchToPropsМетод Create, который используется при вызове компонентаdispatchактивировать соответствующийaction
    • Разделение и рефакторинг редьюсера:
      • По мере роста проекта, если вы напишите редукторы для всех состояний в одной функции, этосложно поддерживать;
      • Редуктор можно разделить, т.е.декомпозиция функции, и, наконец, используйтеcombineReducers()выполнить рефакторинг и слияние;
    • Асинхронное действие: Так как Редуктор — строго чистая функция, в Редюсере нельзя запросить данные, нужно сначала получить данные, а потомdispatch(Action)То есть вот три разных асинхронных реализации:

6. React Hooks

Обычно используется в Reactопределение классаилиопределение функцииСоздайте компонент:

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

  • выгода:

    • 1,Повторное использование компонентов: На самом деле render props/HOC тоже для повторного использования.По сравнению с ними Hooks, как официальный базовый API, самый легковесный, а стоимость трансформации небольшая, что не повлияет на исходную иерархию компонентов и легендарный вложенный ад ;
    • 2,Определения классов более сложны:
      • Различные жизненные циклы сделают логику разбросанной и запутанной, и ее будет непросто поддерживать и управлять;
      • Всегда нужно вниманиеthisуказание на проблему;
      • Повторное использование кода обходится дорого, а использование компонентов более высокого порядка часто приводит к раздуванию всего дерева компонентов;
    • 3.Состояние изолировано от пользовательского интерфейса: Из-за характеристик хуков логика состояния станет менее детализированной, и ее очень легко абстрагировать в пользовательские хуки, состояние и пользовательский интерфейс в компоненте станут более четкими и изолированными.
  • Уведомление:

    • Избегайте вызова хуков в циклах/оценках условий/вложенных функциях, чтобы обеспечить стабильность вызываемой последовательности;
    • Только компоненты определения функции и хуки могут вызывать хуки, избегайте их вызова в компонентах класса или обычных функциях;
    • не может быть вuseEffectиспользуется вuseState, React сообщит об ошибке;
    • Компоненты класса не будут заменяться или отбрасываться, нет необходимости принудительно преобразовывать компоненты класса, и эти два метода могут сосуществовать;
  • важный крючок*:

    • крючок состояния (useState): используется для определения состояния компонента, которое переходит в определение классаthis.stateфункция;
    // useState 只接受一个参数: 初始状态
    // 返回的是组件名和更改该组件对应的函数
    const [flag, setFlag] = useState(true);
    // 修改状态
    setFlag(false)
    	
    // 上面的代码映射到类定义中:
    this.state = {
    	flag: true	
    }
    const flag = this.state.flag
    const setFlag = (bool) => {
        this.setState({
            flag: bool,
        })
    }
    
    • крючки жизненного цикла (useEffect):

    В определении класса есть много функций жизненного цикла, и соответствующая функция также предусмотрена в React Hooks (useEffect), что можно увидеть здесь какcomponentDidMount,componentDidUpdateа такжеcomponentWillUnmountкомбинация.

    • useEffect(callback, [source])принимает два параметра
      • callback: функция обратного вызова ловушки;
      • source: Установите условие запуска, оно будет запускаться только при изменении источника;
      • useEffectКрюк не проходит[source]Когда параметр установлен, по умолчанию функция, возвращенная в последнем сохраненном обратном вызове, будет вызываться первой каждый раз при рендеринге, а затем обратный вызов будет вызываться снова;
    useEffect(() => {
    	// 组件挂载后执行事件绑定
    	console.log('on')
    	addEventListener()
    	
    	// 组件 update 时会执行事件解绑
    	return () => {
    		console.log('off')
    		removeEventListener()
    	}
    }, [source]);
    
    
    // 每次 source 发生改变时,执行结果(以类定义的生命周期,便于大家理解):
    // --- DidMount ---
    // 'on'
    // --- DidUpdate ---
    // 'off'
    // 'on'
    // --- DidUpdate ---
    // 'off'
    // 'on'
    // --- WillUnmount --- 
    // 'off'
    
    • Со вторым параметром мы можем смоделировать несколько общих жизненных циклов:

      • componentDidMount: входящий[], он будет вызван только один раз во время инициализации;
      const useMount = (fn) => useEffect(fn, [])
      
      • componentWillUnmount: входящий[], возвращаемая функция в обратном вызове будет окончательно выполнена только один раз;
      const useUnmount = (fn) => useEffect(() => fn, [])
      
      • mounted: можно инкапсулировать в смонтированное состояние с высокой степенью повторного использования с помощью useState;
      const useMounted = () => {
          const [mounted, setMounted] = useState(false);
          useEffect(() => {
              !mounted && setMounted(true);
              return () => setMounted(false);
          }, []);
          return mounted;
      }
      
      • componentDidUpdate: useEffectОн будет выполняться каждый раз, фактически после исключения DidMount;
      const mounted = useMounted() 
      useEffect(() => {
          mounted && fn()
      })
      
  • Другие встроенные крючки:

    • useContext: получить объект контекста

    • useReducer: Аналогично реализации идей Redux, но недостаточно заменить Redux, его можно понимать как redux внутри компонента:

      • Это не постоянное хранилище, и оно будет уничтожено по мере уничтожения компонента;
      • Он принадлежит компоненту, и каждый компонент изолирован друг от друга, и он не может обмениваться данными, просто используя его;
      • СотрудничатьuseContextГлобальность , может завершить легкий Redux ;(easy-peasy)
    • useCallback: Кэшируйте функцию обратного вызова, чтобы входящий обратный вызов не был каждый раз новым экземпляром функции и вызывал повторную визуализацию зависимого компонента, что приводит к оптимизации производительности;

    • useMemo: Используется для кэширования входящих реквизитов, чтобы каждый раз не перерисовывать зависимые компоненты;

    • useRef: Получить реальный узел компонента;

    • useLayoutEffect:

      • Хук синхронизации обновления DOM. использование сuseEffectАналогично, но отличается от точки времени выполнения.
      • useEffectОн относится к асинхронному выполнению и не ждет выполнения DOM после рендеринга, аuseLayoutEffectОн будет запущен после фактического рендеринга;
      • Обновленное состояние может быть получено;
  • пользовательский крючок(useXxxxx): Основываясь на том, что хуки могут ссылаться на другие хуки, мы можем написать собственные хуки, такие как приведенные выше.useMounted. В другом примере нам нужен собственный заголовок для каждой страницы:

function useTitle(title) {
  useEffect(
    () => {
      document.title = title;
    });
}

// 使用:
function Home() {
	const title = '我是首页'
	useTitle(title)
	
	return (
		<div>{title}</div>
	)
}

7. SSR

ССР, широко известный какрендеринг на стороне сервера(Рендеринг на стороне сервера), человеческая речь: напрямую получать данные на уровне сервера, отображать готовый HTML-файл и напрямую возвращать его в браузер пользователя для доступа.

  • Переднее и заднее разделение: внешний интерфейс изолирован от сервера, и внешний интерфейс динамически получает данные и отображает страницу.

  • Болевые точки:

    • Узкое место в производительности рендеринга первого экрана:

      • Пустая задержка: время загрузки HTML + время загрузки/выполнения JS + время запроса + время рендеринга. В это время страница пуста.
    • SEO-проблемы: Поскольку начальное состояние страницы пусто, сканер не может получить какие-либо достоверные данные на странице, поэтому он не удобен для поисковых систем.

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

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

  • принцип:

    • Node Services: позволяют запускать один и тот же набор кода на переднем и заднем концах.
    • Виртуальный дом: пусть интерфейсный код работает вне браузера.
  • условие: средний уровень Node, React/Vue и другие фреймворки. Структура примерно такая:

  • Процесс развития: (Здесь в качестве примера берется React + Router + Redux + Koa)

    • 1. В том же проектестроитьПередняя и задняя части, общая конструкция:

      • build
      • public
      • src
        • client
        • server
    • 2. Используйте Koa на серверемониторинг маршрутаДоступ к странице:

    import * as Router from 'koa-router'
    
    const router = new Router()
    // 如果中间也提供 Api 层
    router.use('/api/home', async () => {
    	// 返回数据
    })
    
    router.get('*', async (ctx) => {
    	// 返回 HTML
    })
    
    • 3. Получив доступ к URL-адресусоответствоватьМаршрутизация страницы интерфейса:
    // 前端页面路由
    import { pages } from '../../client/app'
    import { matchPath } from 'react-router-dom'
    
    // 使用 react-router 库提供的一个匹配方法
    const matchPage = matchPath(ctx.req.url, page)
    
    • 4. Через настройку постраничной маршрутизациисбор информации. Обычно статическая конфигурация, связанная с SSR, может быть добавлена ​​к маршрутизации страниц для абстрактной логики, которая может обеспечить универсальность логики на стороне сервера, например:

      class HomePage extends React.Component{
      	public static ssrConfig = {
      		  cache: true,
               fetch() {
              	  // 请求获取数据
               }
          }
      }
      

      Обычно есть два случая получения данных:

      • Также используются промежуточные слои.httpВ настоящее время для получения данных метод выборки может быть разделен между интерфейсом и сервером;
      const data = await matchPage.ssrConfig.fetch()
      
      • Средний уровень не использует http, он через некоторыеВнутренний вызов, такие как Rpc или прямое чтение базы данных и т. д. В это время сервер также может напрямую вызывать соответствующий метод для получения данных. Обычно в ssrConfig необходимо настроить определенную информацию, чтобы она соответствовала соответствующему методу сбора данных.
      // 页面路由
      class HomePage extends React.Component{
      	public static ssrConfig = {
              fetch: {
              	 url: '/api/home',
              }
          }
      }
      
      // 根据规则匹配出对应的数据获取方法
      // 这里的规则可以自由,只要能匹配出正确的方法即可
      const controller = matchController(ssrConfig.fetch.url)
      
      // 获取数据
      const data = await controller(ctx)
      
    • 5. Создайте хранилище Redux и сохраните данныеdispatchЗайти внутрь:

    import { createStore } from 'redux'
    // 获取 Clinet层 reducer
    // 必须复用前端层的逻辑,才能保证一致性;
    import { reducers } from '../../client/store'
    
    // 创建 store
    const store = createStore(reducers)
     
    // 获取配置好的 Action
    const action = ssrConfig.action
    
    // 存储数据	
    store.dispatch(createAction(action)(data))
    
    • 6. Инжект Магазин, звонитеrenderToStringРендеринг React Virtual Dom какнить:
    import * as ReactDOMServer from 'react-dom/server'
    import { Provider } from 'react-redux'
    
    // 获取 Clinet 层根组件
    import { App } from '../../client/app'
    
    const AppString = ReactDOMServer.renderToString(
    	<Provider store={store}>
    		<StaticRouter
    			location={ctx.req.url}
    			context={{}}>
    			<App />
    		</StaticRouter>
    	</Provider>
    )
    
    • 7. Упакуйте AppString в полный формат файла HTML;

    • 8. На данный момент полный файл HTML создан. Но это просто чистая статическая страница, без стиля и взаимодействия. Далее мы собираемся вставить JS и CSS. Мы можем получить доступ к пакету, сгенерированному внешним интерфейсомasset-manifest.jsonфайл, чтобы получить соответствующий путь к файлу и вставить его в HTML для справки.

    const html = `
    	<!DOCTYPE html>
    	<html lang="zh">
    		<head></head>
    		<link href="${cssPath}" rel="stylesheet" />
    		<body>
    			<div id="App">${AppString}</div>
    			<script src="${scriptPath}"></script>
    		</body>
    	</html>
    `
    
    • 9. ВыполнитьОбезвоживание данных: Для синхронизации данных, полученных сервером, с интерфейсом. Главное — сериализовать данные, вставить в html и вернуть на фронтенд.
    import serialize from 'serialize-javascript'
    // 获取数据
    const initState = store.getState()
    const html = `
    	<!DOCTYPE html>
    	<html lang="zh">
    		<head></head>
    		<body>
    			<div id="App"></div>
    			<script type="application/json" id="SSR_HYDRATED_DATA">${serialize(initState)}</script>
    		</body>
    	</html>
    `
    
    ctx.status = 200
    ctx.body = html
    

    Tips:

    Здесь есть два особых момента:

    1. использовалserialize-javascriptСериализировать хранилище вместоJSON.stringify, чтобы обеспечить безопасность данных и избежать внедрения кода и XSS-атак;

    2. Используйте json для передачи, вы можете получить более высокую скорость загрузки;

    • 10. Клиентский уровеньданные отстой: при инициализации хранилища используйте обезвоженные данные в качестве данных инициализации для синхронного создания хранилища.
    const hydratedEl = document.getElementById('SSR_HYDRATED_DATA')
    const hydrateData = JSON.parse(hydratedEl.textContent)
    
    // 使用初始 state 创建 Redux store
    const store = createStore(reducer, hydrateData)
    

8. Функциональное программирование

Функциональное программирование – этопарадигма программирования, вы можете понимать это как способ мышления архитектуры программного обеспечения. Он имеет независимый набор теоретических основ и граничных правил, а стремлениеБолее лаконичный, предсказуемый, многократно используемый и легко тестируемый. На самом деле, многие из существующих известных библиотек содержат богатые идеи функционального программирования, такие как React/Redux.

  • Общие парадигмы программирования:

    • Императивное программирование (процедурное программирование): больше связано с шагами по решению проблемы, сообщая компьютеру, что делать шаг за шагом, в форме языка;
    • Программирование, управляемое событиями: подписка на события и запуск, широко используемые в программировании с графическим интерфейсом;
    • Объектно-ориентированное программирование: шаблоны проектирования на основе классов, объектов и методов с тремя основными понятиями: инкапсуляция, наследование и полиморфизм;
    • функциональное программирование
      • Говоря более продвинутым языком, ориентированным на математическое программирование. Боюсь или нет~🥴
  • Идея функционального программирования:

    • чистая функция(Детерминированная функция): это основа функционального программирования, которая может сделать программу гибкой, расширяемой и удобной в сопровождении;

      • Преимущество:

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

        • Не изменяйте параметры;
        • Не зависит и не изменяет какие-либо данные вне функции;
        • Полностью управляемая, параметры одинаковые, возвращаемое значение должно быть одинаковым: например, функция не может содержатьnew Date()илиMath.rando()такие неконтролируемые факторы;
        • ссылка на прозрачность;
      • Многие API или служебные функции, которые мы обычно используем, имеют характеристики чистых функций, например:split / join / map;

    • функциональная композиция: комбинируя несколько функций и вызывая их, вы можете комбинировать функциональные блоки один за другим для достижения конечной цели;

      • сглаженное вложение: В первую очередь надо думать, что самая простая операция объединения функций — это обертка, потому что в JS функции можно использовать и как параметры:

        • f(g(k(x))): Вложенный ад, низкая читабельность, когда функция сложная, легко запутать людей;
        • Идеальная практика:xxx(f, g, k)(x)
      • передача результата: Если вы хотите достичь вышеуказанного метода, то естьxxxЧто функция должна достичь, так это: выполнение передачи результата выполнения между различными функциями;

        • На этом этапе мы можем подумать о нативном методе массива:reduce, может выполняться последовательно в порядке массива, передавая результат выполнения;
        • Итак, мы можем реализовать методpipe, для функциональной композиции:
        // ...fs: 将函数组合成数组;
        // Array.prototype.reduce 进行组合;
        // p: 初始参数;
        const pipe = (...fs) => p => fs.reduce((v, f) => f(v), p)
        
      • использовать: Реализовать функцию горбового именования для подчеркивания именования:

      // 'Guo DongDong' --> 'guo-dongdong'
      // 函数组合式写法
      const toLowerCase = str => str.toLowerCase()
      const join = curry((str, arr) => arr.join(str))
      const split = curry((splitOn, str) => str.split(splitOn));
      
      const toSlug = pipe(
      	toLowerCase,	
      	split(' '),
      	join('_'),
      	encodeURIComponent,
      );
      console.log(toSlug('Guo DongDong'))
      
      • выгода:

        • Скрыть промежуточные параметры, не нужны временные переменные, избежать вероятности ошибок в этой ссылке;
        • Просто обратите внимание на стабильность каждой чистой функциональной единицы, больше не нужно обращать внимание на именование, передачу, вызов и т. д.;
        • Сильная возможность повторного использования, любой функциональный блок можно повторно использовать и комбинировать произвольно;
        • Сильная масштабируемость и низкая стоимость. Например, если вы добавите требование сейчас, вам нужно просмотреть вывод каждой ссылки:
        const log = curry((label, x) => {
        	console.log(`${ label }: ${ x }`);
        	return x;
        });
        
        const toSlug = pipe(
        	toLowerCase,	
        	log('toLowerCase output'),
        	split(' '),
        	log('split output'),
        	join('_'),
        	log('join output'),
        	encodeURIComponent,
        );
        

      Tips:

      На некоторые служебные чистые функции можно напрямую ссылаться.lodash/fp,Напримерcurry/map/splitПодождите, вам не нужно реализовывать это самостоятельно, как мы сделали выше;

    • неизменность данных(неизменяемый): это концепция данных и одна из основных концепций функционального программирования:

      • Адвокат: Объект нельзя изменить после его создания. Когда значение необходимо изменить, возвращается совершенно новый объект вместо прямого изменения исходного объекта;
      • Цель: Обеспечьте стабильность данных. Избегайте неизвестной модификации зависимых данных, что приводит к собственной аномалии выполнения, что может эффективно улучшить управляемость и стабильность;
      • не эквивалентенconst. использоватьconstПосле создания объекта его свойства все еще можно изменить;
      • больше похоже наObject.freeze: Заморозить объект, ноfreezeЕще нет гарантии, что глубинные свойства не будут изменены;
      • immutable.js: библиотека неизменности данных в js, обеспечивающая неизменность данных, широко используется в экосистеме React и значительно повышает производительность и стабильность;
        • trieструктура данных:
          • Структура данных, которая эффективно замораживает объекты, обеспечивая их неизменность;
          • совместное использование структуры: Адрес ссылки на память неизменяемых объектов может использоваться совместно, чтобы уменьшить использование памяти и повысить производительность операций с данными;
    • Избегайте разницы между различными функциямисовместное использование состояния, передача данных использует копии или новые объекты и придерживается принципа неизменности данных;

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

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

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

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

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

    • Функциональное программирование на самом деле является идеей программирования, которая стремится к более тонкой детализации, делит приложение на группы очень маленьких модульных функций, а также объединяет и вызывает потоки рабочих данных;
    • Он выступает за чистые функции/композицию функций/неизменяемость данных и осторожно относится к совместному использованию состояния внутри функций/внешних зависимостей/побочных эффектов;

Tips:

На самом деле, нам трудно и не нужно идеально прорабатывать весь набор идей в процессе собеседования, это лишь поверхностный вкус и некое личное понимание. Блогер тоже юниор-новичок, просто оставайся на поверхности, просто хочешь быть всем полезен, слегка брызгай 🤣;

Я лично считаю: противоречия между этими парадигмами программирования нет, у каждой своиПреимущества и недостатки.

понимать и изучать их концепции и преимущества, разумноСлияние дизайна, используя отличные идеи программирования для улучшения наших приложений;

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

Есть несколько библиотек, которые позволяют быстро получить доступ и использовать функциональные идеи:Underscore.js / Lodash/fp / RxjsЖдать.

Эпилог

На этом этапе, предположительно, все обнаружат, что начали углубляться в некоторые теории и принципы, понять которые не так просто, как в предыдущей статье. Но это также единственный путь, он не может оставаться навсегдаТехники, которые нужно освоить за 5 минутначальство. Не оставайтесь больше на поверхности языка, а поймите более глубокие принципы, шаблоны, архитектуры, причины и следствия, и вы вдруг обнаружите, что вы старший инженер-программист. 😁.

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

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

Tips:

Свяжитесь со мной по почте 159042708@qq.com или QQ / WeChat: 159042708.

Блогер очень старается писать, если не зазвездишься, то реально заплачешь. ~github. 🤑