Como fazer seu aplicativo React renderizar mais rápido
Entregar aplicativos rápidos é fácil, entregar aplicativos rápidos é muito mais difícil. Aqui estão algumas dicas para fazer sua aplicação de reação renderizar seus componentes mais rapidamente.
Enquanto tentamos entregar nossos aplicativos mais rapidamente, muitas vezes esquecemos de focar nas melhores práticas. Depois de um tempo, criamos componentes tão complexos que uma vez que você digita uma chave, os locais que não precisam ser renderizados são renderizados. Aqui a lista do que fazer e do que não fazer.
Usar React.Memo
Normalmente, não queremos que nosso componente seja renderizado toda vez que seu pai é renderizado. Podemos dizer que o React não renderiza nossos componentes, a menos que seja necessário.
// 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
})
Não tenha mais de 4 props por componente
Ter muitos props é sempre problemático, cada props significa mais uma variável a ser considerada quando se trata de desempenho e tornará seu código mais difícil de ler e manter. Em vez de criar componentes com 10 props, crie 3 blocos menores com 3–4 props.
// 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 quando você tiver variáveis que serão calculadas depois que algo mudar. É útil porque, se o seu cálculo demorar ou após o cálculo, você obterá diferentes referências de memória para matrizes ou objetos.
Por favor, não esqueça que quando suas variáveis forem alteradas e você as passar para os filhos, o componente filho será renderizado novamente.
// 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])
Você precisa usar o gancho useCallback quando passar sua função para um componente filho. Não se esqueça que as funções apontarão para um ponto de memória diferente cada vez que seus componentes forem renderizados. Precisamos manter o callback anterior na memória para que não mude cada vez que fizermos algo no componente principal e causar renderização nos filhos.
// 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>
Nós nos acostumamos a usar este gancho para passá-lo para crianças e usar suas instâncias, mas este não é apenas um caso de uso disponível para ele. Podemos usá-lo como algum tipo de cache e mecanismo de estado sem causar nova renderização.
// 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
}
}, [])
Nunca se esqueça disso, cada vez que seus componentes forem renderizados, as variáveis dentro desse componente apontarão para endereços de memória diferentes, quando se trata de tipos primitivos como string e inteiro, isso não causará nenhum problema, mas se você estiver trabalhando com arrays, objetos e funções (porque cada função na verdade são objetos atrás do capô). Vamos ver alguns exemplos
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} />;
}
Você pode pensar que lidar com formulários no react é fácil, mas deixe-me garantir que não é. Quando seus formulários ficam complicados e você precisa fazer algumas validações, etc., você causará muitas renderizações. É por isso que usar uma boa biblioteca é importante. Nos últimos dois anos, tentei muitas bibliotecas diferentes, mas há apenas uma que vale a pena mencionar aqui; forma de gancho de reação.
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>
);
}