Короткие и полезные заметки про React
/pages;
- возможность изоморфного получения данных, используя функцию getServerSideProps();
Для сборки проекта используется Vite. Для получения списка файлов и его экспортов используется метод import.meta.globEager('/pages/**/*.jsx’), который возвращает массив объектов страниц с экспортами. Роуты формируются автоматически с использованием react-router-dom.
hire.jonasgalvez.com.br/2022/ma…-next-jsreactnotes2022JRGpc для скидки.
export async function loader({ params }) {
const note = await getNote(params.noteId);
if (!note) throw new Response("", { status: 404 });
return note;
}
export async function action({ params }) {
await deleteNote(params.noteId);
return redirect("/new");
}
export default function Note() {
const note = useLoaderData();
return (
<div>
<h2>{note.title}</h2>
<div>{note.content}</div>
<Form method="post">
<button type="submit">Delete</button>
</Form>
</div>
);
}
createRoot(document.getElementById("root")).render(
<DataBrowserRouter>
<Route path="/" element={<Root />} loader={rootLoader}>
<Route
path="note/:noteId"
element={<Note />}
loader={noteLoader}
action={noteAction}
errorElement={<div>Note not found</div>}
/>
</Route>
</DataBrowserRouter>
);
beta.reactrouter.com/en/remi…ted/data./pages останется, но появится папка ./app, в которой будут работать новые фичи, например, Server Components.
Появится поддержка вложенных роутов/шаблонов с использованием компонента layout.js. Также появится компонент page.js, который будет использоваться для отображения страницы роута.
Также при использовании Suspense будет улучшение в загрузки данных. Вместо последовательной загрузки роутов и данных будет происходит их параллельная загрузка.
https://nextjs.org/blog/layouts-rfc
const ConditionalComponent = (props) => {
if (!props.visible) return null;
const [value, setValue] = useState();
}
В первую очередь стоит помнить, что babel компилирует код JSX в обычные функции createElement:
const MyComponent = () => {
return <div>Hello world</div>
}
// babel компилирует в
const MyComponent = () => {
return React.createElement("div", null, "Hello world");
};
React рендерит компоненты, вызывает createElement, создает Virtual DOM и выводит пользователю в браузер отрендеренные элементы. Рендер – это просто вызов функции функционального компонента. Функциональный компонент вызывается, возвращается вызов createElement, React сравнивает текущее и новое дерево VDOM и коммитит изменения в DOM.
Важно понимать, что функциональный компонент это функция, которую вызывает React. Это создает проблему: как сохранить данные в стейте при вызовах функции (рендере). Например, следующий код при каждом вызове будет писать в консоль “1”:
const Test = () => {
let a = 1;
console.log(a);
a++;
}
Test(); // 1
Test(); // 1
Test(); // 1
Для того чтобы сохранять состояние стейта между рендерами компонента, React сохраняет состояние во внешнем объекте по ID хука, примерно так:
const state = [];
// Уникальный ID хука, счетчик
let idx = 0;
function useState(init) {
state[idx] = state[idx] ?? { val: init };
return [
state[idx].val,
data => state[idx].val = data
]
}
function Test() {
const [name, setName] = useState('React');
}
Вот почему мы не можем условно отображать хуки: они отслеживаются на основе порядка вызовов, используя в качестве ID счетчик.
Более подробно про хуки написано в блоке Дэна Абрамова: overreacted.io/why-do-…ll-order
import { useState } from 'react'
import useTilg from 'tilg'
function MyApp() {
const [input, setInput] = useState('')
const [count, setCount] = useState(0)
useTilg()`input = ${input}, count = ${count}`
return (
<>
<input onChange={(e) => setInput(e.target.value)} value={input} />
<button onClick={() => setCount(count + 1)}>{count}</button>
</>
)
}
https://github.com/shuding/tilg
import is from './objectIs';
import hasOwnProperty from './hasOwnProperty';
function shallowEqual(objA: mixed, objB: mixed): boolean {
/**
* Проверка равенства примитивных значений.
*/
if (is(objA, objB)) {
return true;
}
/**
* Проверка на то, что текущие аргументы являются объектами и не равны null.
*/
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
/**
* На этом шаге мы уверены что работаем с объектами, получаем ключи объекта.
*/
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
/**
* Сравниваем количество ключей объектов.
*/
if (keysA.length !== keysB.length) {
return false;
}
/**
* Сравнение значения объекта для каждого ключа объекта A с объектом B.
* Проверяется наличие ключа объекта A в объекте B.
*/
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}
return true;
}
Выводы:
- Для сравнения объектов используется Object.is вместо строгого равенства.
- В поверхностном сравнение пустой массив и пустой объект равны.
- Объект с индексами в ключах и массив с такими же значениями одинаковы, т.е. { 0: 2, 1: 3 } равен [2, 3].
- Из-за использования Object.is вместо ===, +0 и -0 не равны, а Number.NaN и NaN равны.
www.chakshunyu.com/blog/ho…in-react
import {interpolate, useCurrentFrame} from 'remotion'
const Title: React.FC<{title: string}> = ({title}) => {
const frame = useCurrentFrame()
const opacity = interpolate(frame, [0, 20], [0, 1], {extrapolateRight: 'clamp'})
return (
<div style={{opacity}}>{title}</div>
)
}
export const MyVideo = () => {
return (
<div style={{ textAlign: "center" }}>
<Title title="Hello World" />
</div>
);
};
https://www.remotion.dev/
Интервью с мейнтенером www.learnwithjason.dev/make-vi…ith-code
const ItemMemo = React.memo(Item);
const CountriesList = ({ countries }) => {
const [sort, setSort] = useState('asc');
const sortedCountries = orderBy(countries, 'name', sort);
const button = <button onClick={() => setSort(sort === 'asc' ? 'desc' : 'asc')}>toggle sorting: {sort}</button>;
return (
<div>
{button}
{sortedCountries.map((country) => (
<ItemMemo country={country} key={country.id} />
))}
{sortedCountries.map((country, index) => (
<ItemMemo country={country} key={index} />
))}
</div>
);
};
В качестве триггера для ререндера будет использоваться кнопка, которая меняет сортировку элементов.
В случае, когда в key используется текущий индекс, при ререндере списка у первого элемента всегда будет key = 0. React считает элементы одинаковыми, если у них одинаковый key. Поэтому при использовании в качестве key текущий индекс массива, элементы списка всегда будут считаться одинаковыми. Но проп country у элементов будет другой, поэтому будет происходить ререндер мемоизированного компонента.
Для списка, где в key используется id, при ререндере элементов React идентифицирует одинаковые элементы и не происходит ререндера элементов.
www.developerway.com/posts/r…ttributeuseEvent. Этот хук будет предназначен для использования в обработчиках событий.
function Chat() {
const [text, setText] = useState('');
const onClick = useEvent(() => {
sendMessage(text);
});
return <SendButton onClick={onClick} />;
}
Код внутри useEvent будет иметь доступ к актуальным значениям пропсов/стейтов в момент вызова. Возвращаемая функция будет иметь одинаковую ссылку, даже если пропсы/стейты, на которые она ссылается, изменились. Также в useEvent не будет массива зависимостей.
При использовании useEffect можно будет исключить из списка зависимостей функции, объявленные через useEvent. Таким образом, эффекты не будут перезапускаться при изменении ссылки на обработчики событий.
function Page({ route, currentUser }) {
// ✅ Stable identity
const onVisit = useEvent(visitedUrl => {
logAnalytics('visit_page', visitedUrl, currentUser.name);
});
useEffect(() => {
onVisit(route.url);
}, [route.url]); // ✅ Re-runs only on route change
// ...
}
Этот хук похож на useCallback, только без массива зависимостей. Однако, есть ограничения на его использование. Например, его нельзя использовать для рендеринга компонентов (т.е. возвращать JSX).
RFC github.com/reactjs…event.md
Обсуждение https://github.com/reactjs/rfcs/pull/220
function App () {
return (
<article>
<h2>Title</h2>
<p>Some content</p>
</article>
)
}
Компонент выше содержит четыре FiberNode: App, <article>, <h2>, <p>. Каждый FiberNode содержит ссылки на соседние FiberNode, родительский и дочерний. Рассмотрим основные свойства FiberNode, сопоставив их значения с примером выше:
- child - дочерний элемент. AppFiberNode.child === ArticleFiberNode
- sibling - соседний элемент. H2FiberNode.sibling === PFiberNode
- return - родительский элемент. H2FiberNode.return === ArticleFiberNode
В процессе рендера для того, чтобы узнать о необходимых изменениях, в каждом FiberNode есть свойство alternate. В нем хранится ссылка на копию текущего FiberNode, который был закоммичен в DOM. В процессе рендера происходит сравнение текущего FiberNode и его копии в alternate, и, если они отличаются, то происходит коммит изменений.
indepth.dev/posts/1…in-reactreactnotes2022JRGpc
Билеты, подробности и первая программа— на сайте.
function Counter({ count }) {
const logCount = () => {
// переменной count берется из внешнего окружения
console.log('count', count)
}
return <button onClick={logCount}>Show me the count</button>
}
Замыкание- это когда функция может взаимодействовать с окружающими переменными. Само замыкание создается каждый раз, когда создается функция, в момент создания функции.
В функциональных компонентах замыкание работает как надо, при каждом рендере компонента объявленные внутри компонента функции создаются заново и получают в свое окружение новые пропсы.
Чаще всего это не вызывает никаких проблем, однако, если понадобится мемоизация функции, то нужно будет оборачивать функцию в useCallback.
При работе с хуками рекомендуется использовать линтер react-hooks/exhaustive-deps, который подсвечивает неопределенные зависимости для хука useCallback. Если игнорировать список зависимостей useCallback, то используемые внутри него переменные компонента будут устаревшими.
tkdodo.eu/blog/ho…closures
const editorConfig = {
// The editor theme
theme: ExampleTheme,
// Handling of errors during update
onError(error) {
throw error;
},
// Any custom nodes go here
nodes: [
HeadingNode,
ListNode,
ListItemNode,
QuoteNode,
CodeNode,
CodeHighlightNode,
TableNode,
TableCellNode,
TableRowNode,
AutoLinkNode,
LinkNode
]
};
export default function Editor() {
return (
<LexicalComposer initialConfig={editorConfig}>
<div className="editor-container">
<ToolbarPlugin />
<div className="editor-inner">
<RichTextPlugin
contentEditable={<ContentEditable className="editor-input" />}
placeholder={<Placeholder />}
/>
<HistoryPlugin />
<TreeViewPlugin />
<AutoFocusPlugin />
<CodeHighlightPlugin />
<ListPlugin />
<LinkPlugin />
<AutoLinkPlugin />
<ListMaxIndentLevelPlugin maxDepth={7} />
<LexicalMarkdownShortcutPlugin />
</div>
</div>
</LexicalComposer>
);
}
https://lexical.dev/