Jak przyspieszyć renderowanie aplikacji React
Szybkie dostarczanie aplikacji jest łatwe, dostarczanie szybkich aplikacji jest o wiele trudniejsze. Oto kilka wskazówek, dzięki którym aplikacja reagująca szybciej renderuje komponenty.
Chociaż staramy się dostarczać nasze aplikacje szybciej, często zapominamy o skupieniu się na najlepszych praktykach. Po pewnym czasie tworzymy komponenty tak złożone, że po wpisaniu klucza miejsca, które nie muszą być renderowane, zostaną wyrenderowane. Oto lista tego, co robić, a czego nie.

Użyj React.Memo
Zwykle nie chcemy, aby nasz komponent renderował się za każdym razem, gdy renderowany jest jego rodzic. Możemy powiedzieć, że React nie renderuje naszych komponentów, chyba że musi.
// Wrong usage
// Without React.memo this component will get rendered each time
// something causes its parent to render
const YourHeavyComponent = (name, title, onClick) => {
// Render something really heavy like whole page here
}
export default YourHeavyComponent
// Good usage
// This component will not render unless name, title or onClick changes
const YourHeavyComponent = (name, title, onClick) => {
// Render something really heavy like whole page here
}
export default React.memo(YourHeavyComponent)
// This component will not render unless title changes
const YourHeavyComponent = (name, title, onClick) => {
// Render something really heavy like whole page here
}
// This usage might better for some stations, use this second argument
// to specify which props will cause render
export default React.memo(YourHeavyComponent, (prevProps, nextProps) => {
return prevProps.title === nextProps.title
})
Nie używaj więcej niż 4 rekwizytów na komponent
Posiadanie wielu rekwizytów jest zawsze problematyczne, każdy rekwizyt oznacza jedną zmienną więcej do rozważenia, jeśli chodzi o wydajność, i sprawi, że twój kod będzie trudniejszy do odczytania i utrzymania. Zamiast tworzyć komponenty z 10 rekwizytami, utwórz 3 mniejsze fragmenty z 3–4 rekwizytami.
// this component has too many filters
// it's not a good idea to add filters inside this component
// instead we need to create a seperate component to add filter inputs
// and we can also remove title from here
const List = (title, items, sortBy, keys, filters, onFilterChange, children, pagination) => {
// Render something really heavy like whole page here
}
// here userslist wont rendered unless we get a new list
// pagination payload won't cause rendering in filters
const [payload, setPayload] = useState()
const [list, setList] = useState([])
return <>
<Filters onFilterChange={setPayload} filters={payload.filters} />
<UserList items={list}/>
<Pagination onPaginationChange={setPayload} pagination={payload.pagination} />
</>
hook, gdy masz zmienne, które zostaną obliczone po zmianie. Jest to pomocne, ponieważ jeśli obliczenia wymagają czasu lub po obliczeniu otrzymasz różne odniesienia do pamięci dla tablicy lub obiektów.
Proszę nie zapominać, że kiedy twoje zmienne zostaną zmienione i przekażesz je dzieciom, komponent children zostanie ponownie wyrenderowany.
// here we use useMemo for purely caching purposes, if we don't use useMemo
// each time this components rendered the calculation will have to re-run
const heavyCalculated = useMemo(() => doSomeHeavyCalculation(variable1), [variable])
// an example of wrong usage
// each time this components get rendered this
// styles variable will point to different memory address
// and this will cause rerender of YourHeavyComponent
const styles = {
container:{
marginLeft: left,
marginRight: right,
marginTop: top,
marginBottom: bottom,
}
}
// correct usage of useMemo
// this will cache your value and its memory point won't change
// so even if this components gets rendered your YourHeavyComponent won't be rendered again
const styles = useMemo(() => ({
container:{
marginLeft: left,
marginRight: right,
marginTop: top,
marginBottom: bottom,
}
}), [left, right, top, bottom])
Musisz użyć haka useCallback, gdy przekażesz swoją funkcję do komponentu potomnego. Nie zapominaj, że funkcje będą wskazywać inny punkt pamięci za każdym razem, gdy komponenty zostaną wyrenderowane. Musimy zachować poprzednie wywołanie zwrotne w pamięci, aby nie zmieniało się za każdym razem, gdy zrobimy coś w głównym komponencie i spowoduje renderowanie w potomkach.
// wrong usage
// each time App components get rendered for some reason
// onClickCallback will be recreated and it will point to different memory address
// so it will cause YourHeavyComponent to re render
const onClickCallback = () => {
// do some stuff
}
return <div>
<YourHeavyComponent onClick={onClickCallback} />
</div>
// good usage of onClickCallback
// each time App components if your variable1 don't change
// onClickCallback will point to same memory point
// so UourHeavyCallback won't render.
const onClickCallback = () => {
// do some stuff
}
return <div>
<YourHeavyComponent onClick={onClickCallback} />
</div>
Przyzwyczailiśmy się do używania tego haka do przekazywania go dzieciom i korzystania z ich instancji, ale to nie tylko dostępny dla niego przypadek użycia. Możemy go użyć jako pewnego rodzaju mechanizmu buforowania i stanu bez powodowania ponownego renderowania.
// in this example we look for useRef value instead of setting
// sent reference is being used for checking if we send this message before or not
// this could hold in the state but if we put into state it will cause render
// it into state, because we don't want to re-render after we set sent.current
const sent = useRef(false)
const postMessage = useCallback(() => {
if (!sent.current) {
// make your api call here
sent.current = true
}
}, [])
Nigdy o tym nie zapominaj, za każdym razem, gdy renderujesz komponenty, zmienne wewnątrz tego komponentu będą wskazywać inny adres pamięci, jeśli chodzi o prymitywne typy, takie jak string i integer, nie spowoduje to żadnych problemów, ale jeśli pracujesz z tablicami, obiektami i funkcja (ponieważ każda funkcja jest w rzeczywistości obiektami za maską). Zobaczmy kilka przykładów
const InlineTest = () => {
// the usage below will cause YourHeavyComponent to render if
// something causes InlineTest component to render
return <YourHeavyComponent style={{
marginLeft: 10,
marginRight: 10
}} />
}
const style = {
marginLeft: 10,
marginRight: 10
}
// We move styles outside of component
// this way style will point the same memory address regardless of
// how many tames InlineTest gets rendered
const InlineTest = () => {
// the usage below will cause YourHeavyComponent to render if
// something causes InlineTest component to render
return <YourHeavyComponent style={style} />
}
const InlineTest = () => {
// the usage below will cause YourHeavyComponent to render if
// something causes InlineTest component to render
// because onClick will be assigned to a new function each time
return <YourHeavyComponent onClick={() => {
console.log('clicked');
}} />;
}
import {useCallback} from "react";
const InlineTest = () => {
// the usage below is correct way to do it
// using useCallback will make sure onClick function
// is the same between renders and it won't cause render in below component
const onClick = useCallback(() => {
console.log('clicked');
}, [])
return <YourHeavyComponent onClick={onClick} />;
}
Możesz pomyśleć, że obsługa formularzy w React jest łatwa, ale gwarantuję, że tak nie jest. Kiedy twoje formularze się skomplikują i będziesz musiał wykonać kilka walidacji itp., Spowodujesz zbyt wiele renderowań. Dlatego tak ważne jest korzystanie z dobrej biblioteki. W ciągu ostatnich kilku lat wypróbowałem wiele różnych bibliotek, ale tylko jedna jest warta wzmianki; Reaguj w formie haka.
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = data => console.log(data);
return (
/* "handleSubmit" will validate your inputs before invoking "onSubmit" */
<form onSubmit={handleSubmit(onSubmit)}>
{/* register your input into the hook by invoking the "register" function */}
<input defaultValue="test" {...register("example")} />
{/* include validation with required or other standard HTML validation rules */}
<input {...register("exampleRequired", { required: true })} />
{/* errors will return when field validation fails */}
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
);
}