Произведение искусства Фернандо Хорхе
Читать в свете, темной или сепии теме
PreaCt – это имя домохозяйства [Web dev] на данный момент. Почти каждый веб-разработчик, который был в этом бизнесе дольше 2 лет, слышал об этом, и, возможно, даже попробовал сами. И, вероятно, достиг того же вывода, как я: Это круто !! 😻 .
Итак, сегодня я собираюсь сделать глубокий погружение в исходный код PrECAC, и замечание на некоторые интересные вещи, которые я нахожу там.
Если вы не знакомы, PreaCt – это 3 КБ альтернатива 42 КБ реагировать, по Джейсон Миллер . Он полностью совместим с API React и поддерживает все пакеты, которые полагаются на реагирование. Это круто так.
Прежде чем мы посмотрим на код, я возмещу о некоторых вещах о преобразовании.
Написано в Teadercript, но не совсем …
Исходный код PrECACT написан в Teadercript, но сами основные файлы нет. Основные файлы с функциональными возможностями написаны на простом JavaScript, но они используют Jsdoc Чтобы потянуть типы из файлов определения TypeyctScript (.d.ts).
Пример:
Это Типы.d.ts файл:
export type RenamedType = number | null;
И вот файл JS
/**
* @param {import('./types').RenamedType} a
* @param {import('./types').RenamedType} b
* @returns
*/
function sum(a, b) {
return a + b;
}
Как видите, код JavaScript просто: JavaScript. Вы не увидите тип типа TeamptScript, указанный в нем. Скорее все данные типа указаны в комментариях, которые полностью игнорируются. Есть целая статья о Использование Teamscript без Typeycript , но TLDR; Вот будет: избегайте разработки времени. Если это просто просто JS, вам не нужно запускать файловый наблюдатель для трансильных файлов по мере их изменения. Просто беги то, что у тебя есть. И у вас уже есть компилятор Tymdercript, работая все время без вы явно не запущенного: Ваш VSCode.
Это очень интересный подход, и я вижу, что все больше и больше библиотек подойдут, особенно библиотеки без пользовательских интерфейсов (для библиотек интернет-пользовательских интерфейсов, у вас уже есть веб-сервер, поэтому добавление в Teamscript в инструментарии не изменится, продолжается многое и добавить Teamscript)
Очень хорошо написанный код
Мне не нужно просто сказать это вслух, но исходный код PrECACT очень хорошо написан и прокомментирован, так как вы ожидаете от такой первостепенной базы.
Это много повторно повторно
Ага. Одной из причин PREACT настолько мала, это то, что он повторно использует свою экспортированную функцию в других экспортированных функциях. МНОГО!! Я покажу вам некоторые места, где это происходит
Это не будет полным поломкой и не будет последовательным. PREACT – это довольно большая библиотека для покрытия в блоге пост, поэтому я просто прикрымую интересные части.
Итак, давайте начнем !! Мы посмотрим на некоторые интересные вещи в Core модуль (то есть. , тот, когда вы ввести Импорт {} из «ПреизACT» ), тогда мы доберемся до крючков
index.js.
Как и традиция, давайте начнем с index.js файл:
export { render, hydrate } from './render';
export {
createElement,
createElement as h,
Fragment,
createRef,
isValidElement,
} from './create-element';
export { Component } from './component';
export { cloneElement } from './clone-element';
export { createContext } from './create-context';
export { toChildArray } from './diff/children';
export { default as options } from './options';
Известные очки: H , который представляет собой PREACT JSX Factory, на самом деле назван творчество Отказ Так же, как Отреагировать . Но экспортируется как H Потому что это позволяет писать RAW PREACT (без JSX), также потому, что он был изначально вдохновлен от Hyperscript 👇
h('div', { class: 'haha' }, h('span', { key: 34 }, h('h1', {}, h('span', {}, 'Whoa'))));
Также примечательно, что он экспортирует творчество Как это тоже, чтобы поддерживать совместимость с Отреагировать
create-element.js.
import options from './options';
export function createElement(type, props, children) {
/*...*/
}
export function createVNode(type, props, key, ref, original) {
/*...*/
}
export function createRef() {
return { current: null };
}
export function Fragment(props) {
return props.children;
}
export const isValidElement = (vnode) => vnode != null && vnode.constructor === undefined;
Опущено творчество и Createvnode как они довольно большой.
Createref.
Дай мне удивить тебя. Ref S в P/Ract в основном используется для инкапсуляции значений, которые не должны вызывать повторные рендеры и не восстанавливаются на каждом повторном рендере. Давайте посмотрим, как определяет договор:
export function createRef() {
return { current: null };
}
Реф – просто объект с Текущий Собственность набор на null Отказ Это всегда рекламируется как это, но я никогда не думал, что это на самом деле объект внутри тоже.
Маленький клип меня, когда я обнаружил это 👇
Фрагмент
Далее у нас есть Фрагмент Отказ Это также еще одна удивительная вещь.
export function Fragment(props) {
return props.children;
}
Фрагмент, просто возвращает его дети Отказ Это все! 🤯🤯.
Я знал, что это то, что это Предполагается делать, но я всегда изображен какой-то сложный код. Не понимал, что это была просто эта супер простая вещь.
isValidelement.
/**
* Check if a the argument is a valid Preact VNode.
* @param {*} vnode
* @returns {vnode is import('./internal').VNode}
*/
export const isValidElement = (vnode) => vnode != null && vnode.constructor === undefined;
Просто проверяя, передается ли текущий виртуальный узел DOM к нему действительным или нет. Опять же, один лайнер, супер маленький, но вот узор, который я узнал, посмотрев на этот код только. Обратите внимание @ verturns {vnode Import ('./Внутренний'). Vnode} в jsdoc. Код в основном использует типовые охранники. Прямо в jsdoc. Я не видел этого шаблона раньше, что все больше доказательств того, что код чтения, написанный тем, что они умнее, чем вы можете сделать вас лучшим разработкой.
Render.js.
Помните файл index.jsx, где вы инициализируете свой Преобразование приложение
import { render, h } from 'preact';
import App from './App';
render( , document.querySelector('#app'));
Это оказывать Функция 👇.
export function render(vnode, parentDom, replaceNode) {
if (options._root) options._root(vnode, parentDom);
// We abuse the `replaceNode` parameter in `hydrate()` to signal if we are in
// hydration mode or not by passing the `hydrate` function instead of a DOM
// element..
let isHydrating = typeof replaceNode === 'function';
// To be able to support calling `render()` multiple times on the same
// DOM node, we need to obtain a reference to the previous tree. We do
// this by assigning a new `_children` property to DOM nodes which points
// to the last rendered tree. By default this property is not present, which
// means that we are mounting a new tree for the first time.
let oldVNode = isHydrating ? null : (replaceNode && replaceNode._children) || parentDom._children;
vnode = ((!isHydrating && replaceNode) || parentDom)._children = createElement(Fragment, null, [
vnode,
]);
// List of effects that need to be called after diffing.
let commitQueue = [];
diff(
parentDom,
// Determine the new vnode tree and store it on the DOM element on
// our custom `_children` property.
vnode,
oldVNode || EMPTY_OBJ,
EMPTY_OBJ,
parentDom.ownerSVGElement !== undefined,
!isHydrating && replaceNode
? [replaceNode]
: oldVNode
? null
: parentDom.firstChild
? EMPTY_ARR.slice.call(parentDom.childNodes)
: null,
commitQueue,
!isHydrating && replaceNode ? replaceNode : oldVNode ? oldVNode._dom : parentDom.firstChild,
isHydrating
);
// Flush all queued effects
commitRoot(commitQueue, vnode);
}
export function hydrate(vnode, parentDom) {
render(vnode, parentDom, hydrate);
}
Во-первых, Очень хорошо прокомментировал Отказ
От того, насколько хорошо я могу почувствовать ситуацию здесь, оказывать Функция в основном делает Причудливо Чтобы сохранить все изменения, необходимые для выполнения. Далее Различать Функция принимает в старый Vnode и новый VNODE, имея чувство ситуации и выяснение, какие узлы DOM должны быть обновлены, а также заполнение Причудливо Отказ
Тогда его в основном совершать Эти изменения. Это так же, как то, как мы делаем это в базе данных. Мы выполняем некоторую операцию в пакете, коммит, поэтому все они применяются один за другим одновременно.
Я хотел бы покрыть Различать в блоге пост тоже, но его настолько большой у него есть свой 500 линий Длинный файл 😵. Все, что вам нужно знать, его работа состоит в том, чтобы понять, какие узлы DOM должны быть обновлены, и что, чтобы сохранить то же самое.
гидрат
Эта функция очень интересная, как это ничего, кроме как называя оказывать функция. Но что-то еще интересное, его прохождение по сам как 3-й аргумент. И если вы снова смотрите в оказывать Функция, на самом деле на самом деле имеет состояние, если функция передана на нее названа гидрат Отказ Черт возьми, есть даже комментарий о злоупотребление 3-й аргумент 😂. Эти люди слишком умны !!
Я, вероятно, устанавливаю мой повторный предел, но Дарн !! Повторное использование преизду само по себе действительно, Darn хорошо !!!
create-context.js.
Это, вероятно, будет возбуждено вам, так как контекст – очень, очень любимый API большинством разработчиков P/React. Это не всегда так, но упреждающий текст Крючки сделали это очень легко в использовании контекст. Слишком легко !!
const { lemonsCount, setLemonsCount } = useContext(lemonsContext);
import { enqueueRender } from './component';
export let i = 0;
export function createContext(defaultValue, contextId) {
contextId = '__cC' + i++;
const context = {
_id: contextId,
_defaultValue: defaultValue,
/** @type {import('./internal').FunctionComponent} */
Consumer(props, contextValue) {
return props.children(contextValue);
},
/** @type {import('./internal').FunctionComponent} */
Provider(props) {
if (!this.getChildContext) {
let subs = [];
let ctx = {};
ctx[contextId] = this;
this.getChildContext = () => ctx;
this.shouldComponentUpdate = function (_props) {
if (this.props.value !== _props.value) {
subs.some(enqueueRender);
}
};
this.sub = (c) => {
subs.push(c);
let old = c.componentWillUnmount;
c.componentWillUnmount = () => {
subs.splice(subs.indexOf(c), 1);
if (old) old.call(c);
};
};
}
return props.children;
},
};
// Devtools needs access to the context object when it
// encounters a Provider. This is necessary to support
// setting `displayName` on the context object instead
// of on the component itself. See:
// https://reactjs.org/docs/context.html#contextdisplayname
return (context.Provider._contextRef = context.Consumer.contextType = context);
}
Этот файл, этот небольшой файл, это все, что есть в основном контексте API. Эти 42 строки делают так много (за исключением комментариев).
Итак, давайте проверим Потребитель Отказ Вернитесь на длительное время назад и помните, что мы использовали для использования Потребитель Для доступа к контексте данные.
Вот как это выглядит
{(data) => Hello {data}}
Это выглядит довольно управляемой, но он может ухудшаться, когда ваш код вырос.
Итак, если мы посмотрим на код Потребитель Это просто это:
Consumer(props, contextValue) {
return props.children(contextValue);
},
Вот и все!! Это ожидает его дети быть функцией, и это просто вызывает его с контекстными данными. Внезапно Потребитель Пример шаблона выше имеет смысл 🤯🤯.
Что касается Провайдер , Что это делает, в основном, изменяет жизненные циклы своего родительского компонента для наблюдения за изменениями состояния контекста.
Наконец, есть вернуть Заявление на дне. Последняя строка – это большой мутационный трюк, который часто используется при кодировании классических языков, таких как C, C ++, Java и т. Д., То есть, возвращая переменную и мутируя ее одновременно. Здесь это мутируют его ради преиздота devtools, чтобы показать DisplayName в devtools, как реагировать devtools.
И теперь, время для раздела, которое вы, вероятно, пришли сюда полностью: Крючки !!
Итак, во-первых, крючки расположены в отдельном каталоге. В отличие от реакции, все отказывается в преобразовании, что делает минималистский во мне радуется. Есть намеренность в каждой вещи, которую вы делаете здесь. Я так.
Итак, давайте начнем с самого первого крючка, который вы когда-либо сталкивались: stestate.
Но будьте осторожны, крутой лежит здесь 😈
stestate.
Это, это Уместите :
export function useState(initialState) {
currentHook = 1;
return useReducer(invokeOrReturn, initialState);
}
Mindblown верно? Как видите, утилизация в основном звонит Успенсер , который является еще одним стандартным реагированным крюком. Так в основном, Уместите это просто псевдоним Успенсер , ты мог бы сказать.
Переменные invokeormunt. и CurrentHook определяются в одном файле, в модульном объеме и управлению PreaCt.
И лемме дают вам другой самородок. Смотрите CurrentHook выражение? Угадай, что: это не нужно в основной функциональности. Это существует исключительно для Preact devtools Отказ То есть, если Devtools не было рассмотрением, этот код может также быть:
const useState = (initialState) => useReducer(invokeOrReturn, initialState);
Буквально один лайнер !! 🤯🤯🤯🤯.
Опять же, интенсивный фокус на всей самооценке, которую я продолжаю повторять.
Весь тяжелый подъем здесь делается Успенсер Так что давайте посмотрим на это дальше.
упред
export function useReducer(reducer, initialState, init) {
/** @type {import('./internal').ReducerHookState} */
const hookState = getHookState(currentIndex++, 2);
hookState._reducer = reducer;
if (!hookState._component) {
hookState._value = [
!init ? invokeOrReturn(undefined, initialState) : init(initialState),
(action) => {
const nextValue = hookState._reducer(hookState._value[0], action);
if (hookState._value[0] !== nextValue) {
hookState._value = [nextValue, hookState._value[1]];
hookState._component.setState({});
}
},
];
hookState._component = currentComponent;
}
return hookState._value;
}
Я признаю, что я не полностью понимаю, что здесь происходит 😅, но то, что привлекло мое внимание здесь: посмотрите на hookstate._value = [ Декларация внутри Если блокировать. Это массив с 2 элементами. 1-й элемент – это просто значение. 2-й – это функция.
Подожди секунду. 1-й элемент значение, 2-й элемент функция …
Святые курить !!! Это [Состояние, SetState] Пара вернулась из Уместите 😵😵.
const [state, setState] = useState(Infinity); // 😈
Если бы это не взорвало свои мозги друг от друга, я не знаю, что будет.
Следующий: 2-й самый известный крюк!
useffect.
export function useEffect(callback, args) {
/** @type {import('./internal').EffectHookState} */
const state = getHookState(currentIndex++, 3);
if (!options._skipEffects && argsChanged(state._args, args)) {
state._value = callback;
state._args = args;
currentComponent.__hooks._pendingEffects.push(state);
}
}
Ага!!! Обратите внимание на Если Блок здесь. Мы проверяем на 2 вещи.
! Опции ._skipeffects – PreaCT имеет параметры Config, где вы можете отключить все побочные эффекты. Так что запускать это Useffect Мы должны убедиться, что это безопасно для запуска эффектов.
argsChanged (State._Args, Args): Это очень интересно. Помните 2-й аргумент, который вы переходите наuseffect.?
useEffect(() => {
/* Do epic shit */
}, [emojiUpdated]);
Угадай, что, Аргенда Является ли функция, ответственная за проверку, были ли изменения в зависимости, передаваемых на Useffect Отказ Вот, мы передаем это State._args Список аргументов поддерживается PREACT для этого определенного крюка, и 2-й аргумент является новым набором зависимостей. Если какие-либо изменения обнаружены, эта функция возвращает true, и эффект снова работает.
Что касается Аргенда Функция, это просто это 👇
function argsChanged(oldArgs, newArgs) {
return (
!oldArgs ||
oldArgs.length !== newArgs.length ||
newArgs.some((arg, index) => arg !== oldArgs[index])
);
}
Его в основном проверяют, существуют ли Oldarg даже вначале. Почему?
Потому что список зависимостей передал useffect. Сама может быть государством, держащим массив.
const [deps, setDeps] = useState([]);
useEffect(() => {
/* Do epic shit */
}, deps);
OFC, простая причина может быть то, что вы не проходили массив. Это то, что большинство людей будут делать, а не этот выше метод 😅.
2-й, его проверка, если длина списка аргументов отличается или нет. Это умный ход, потому что, если сам размер массива меняется, вам не нужно проходить и проверять каждое значение.
Самый дешевый звонок функции – это тот, который вы никогда не делаете ~~ Джейсон Миллер
И, наконец, когда все эти условия верны, мы наконец проверяем, используя ли значения, используя arr.some метод.
Из того, что я могу сказать, эта функция написана таким образом, чтобы остановиться как можно скорее. Вы могли бы написать эту же функцию таким образом, чтобы она сделала все эти вещи, а потом Скажите результат. Здесь, через некоторые умные короткое замыкание Они сделали эту функцию довольно эффективной.
Uselayouteffect.
export function useLayoutEffect(callback, args) {
/** @type {import('./internal').EffectHookState} */
const state = getHookState(currentIndex++, 4);
if (!options._skipEffects && argsChanged(state._args, args)) {
state._value = callback;
state._args = args;
currentComponent._renderCallbacks.push(state);
}
}
Этот крючок очень, очень интересный. Если вы прочитаете код Useffect Вы обнаружите, что они точно такие же, за исключением самой последней линии.
В Useffect , это 👇
currentComponent.__hooks._pendingEffects.push(state);
Тогда как здесь это 👇
currentComponent._renderCallbacks.push(state);
В Useffect , эффекты для выполненных подняты в очередь, которая выполняется асинхронно.
Тогда как в Uselayouteffect эффекты подталкиваются к оказывать Обратные вызовы, делая его с нетерпением, когда происходит рендеринг. Вот почему его называется использование * Макет *Эффект.
Следующий, другой крючок, который взорвет ваш разум и изменит, как вы пишете свой Ref с. YEPP, вы догадались правильно, его УСЭРЕФ 😎.
УСЭРЕФ 😎.
Реализация этого крюка так круто, что я не могу не поставить солнцезащитные очки Emoji перед ним 😁
export function useRef(initialValue) {
currentHook = 5;
return useMemo(() => ({ current: initialValue }), []);
}
Если вы заметите, УСЭРЕФ просто Угемер В маскировке с объектом, имеющим одно свойство: Текущий со значением null.
Итак, эффективно, вы можете написать свои ссылки в виде памяти
const containerElementRef = useMemo(() => ({ current: null }), []);
Не принимайте это слишком серьезно, хотя. Лучше, если элементы Refs назначены на правильное УСЭРЕФ Только значения, так как это уборщик, синтаксис построен вокруг него.
Что я хочу указать в том, что это много людей, особенно начинающих, приравнивают Ref Как то, что держит DOM-ссылки, и все это делают. Что не очень хорошо.
Но когда вы посмотрите на этот код и посмотрите, что Ref – это просто значение, которое кэшируется для жизненного цикла компонента, в топливах. Ментальный блок и чувство магии уходят, и вы чувствуете себя полностью в контроле.
UseCallback
export function useCallback(callback, args) {
currentHook = 8;
return useMemo(() => callback, args);
}
И вот еще один крючок, который просто Угемер под капотом. Это дает мне лолс 😂😂. На данный момент я просто хихикаю молча видишь, что все в презрачных крюках только Угемер Отказ
Usememo.
Ааа, звезда шоу, Угемер ! 🤩 Ну наконец то!
export function useMemo(factory, args) {
/** @type {import('./internal').MemoHookState} */
const state = getHookState(currentIndex++, 7);
if (argsChanged(state._args, args)) {
state._value = factory();
state._args = args;
state._factory = factory;
}
return state._value;
}
Этот довольно просто. Получите состояние для этого конкретного крючка, сравните предыдущие зависимости к новому и обновлению значений, а заводская функция передана ему, если что-то изменится.
И это снова так мало, это заставляет меня смеяться, а также плакать. Серьезно, проходя через эту кодовую базу, дает мне огромный усиленный синдром каждый раз. Архитектура настолько чертовски хорошо сделана, что дублирование кода не нужна здесь нигде, поэтому все супер маленькие. Хорошо сделанные преобразователи 🥲
упреждающий элемент
Один из самых любимых крючков всех времен, упреждающий текст 😍.
export function useContext(context) {
const provider = currentComponent.context[context._id];
// We could skip this call here, but than we'd not call
// `options._hook`. We need to do that in order to make
// the devtools aware of this hook.
/** @type {import('./internal').ContextHookState} */
const state = getHookState(currentIndex++, 9);
// The devtools needs access to the context object to
// be able to pull of the default value when no provider
// is present in the tree.
state._context = context;
if (!provider) return context._defaultValue;
// This is probably not safe to convert to "!"
if (state._value == null) {
state._value = true;
provider.sub(currentComponent);
}
return provider.props.value;
}
Много комментариев здесь. Если я удалю все их
export function useContext(context) {
const provider = currentComponent.context[context._id];
const state = getHookState(currentIndex++, 9);
state._context = context;
if (!provider) return context._defaultValue;
if (state._value == null) {
state._value = true;
provider.sub(currentComponent);
}
return provider.props.value;
}
Ты шутишь, что ли!?!? Всего 7 строк в теле, и у вас есть самое большое упрощение, которое пришло при запуске реактивных крюков. Что это за колдовство!! 😑😑.
Здесь заметные точки: если провайдер не обнаружен, он возвращает значение по умолчанию, благодаря этому 1 лайнеру, если оператор. И если здесь нет значения, PreaCt подписывает текущий компонент в контекст.
usereerrorboundary.
export function useErrorBoundary(cb) {
/** @type {import('./internal').ErrorBoundaryHookState} */
const state = getHookState(currentIndex++, 10);
const errState = useState();
state._value = cb;
if (!currentComponent.componentDidCatch) {
currentComponent.componentDidCatch = (err) => {
if (state._value) state._value(err);
errState[1](err);
};
}
return [
errState[0],
() => {
errState[1](undefined);
},
];
}
Я огромный, огромный поклонник Преобразование Для предоставления usereRororboundary крюк. В реакции, если вы хотите, чтобы границы ошибок вы должны создать компонент класса самостоятельно и установить в корне своего дерева компонентов. Принимая во внимание, что он по умолчанию по умолчанию в PreaCT, что заставляет мое сердце трепетать 😅
Здесь заметные точки: этот крючок в основном устанавливает ComponentDidcatch Жизненный цикл, чтобы поймать ошибки и делать то, что вы говорите этот крюк, чтобы сделать. Его более или менее такими же, как вы сами делаете классную компонент, только вам не нужно ничего устанавливать здесь, просто бросьте этот крюк в любой компонент, вот в верхней части компонентного дерева.
Это это для крючков. Я не покрыл Useebugvalue и UseimpratationHandle , как я никогда не должен был использовать Useebugvalue и UseimpratationHandle считается небезопасным для использования ¯ \ _ (ツ) _/¯ ¯
Обратите внимание, как я говорил, код очень просто. Ну, это супер легко читать, потому что это так просто, но писать это сложно. Простота редко легко, ее всегда сложнее достичь. Написание хорошего, эмоционального ролькоастера в 100 слов сложно. Выбросить чрезмерную одежду трудно. Наличие чистого стола сложнее, чем загроможденный стол.
И сделать 3КБ кода для того, что изначально было 42 КБ сложно.
Вычитание сложнее, чем добавление, разделение сложнее, чем умножение.
Делать преимущество ни в коем случае не было бы легкой задачей, но Джейсон сделал это удивительно, и все участники, которые вскочили в позже, сделали его еще больше, пока все еще сохраняют все маленькое и проще. Это монументальная задача. Шляпы в презрачную команду для этих усилий
Это это на сегодня!
Подписание !!
Оригинал: “https://dev.to/puruvj/the-zen-of-preact-s-source-code-59g2”