- [Cuối cùng `children` là cái gì trong React?](#cuối-cùng-children-là-cái-gì-trong-react)
- [React Element là gì?](#react-element-là-gì)
- [Cập nhập Element](#cập-nhập-element)
- [Lời giải thích](#lời-giải-thích)
const ChildComponent = () => {
console.log("ChildComponent re-render")
return <div>Child</div>
}
const MovingComponent = () => {
const [state, setState] = useState({ x: 100, y: 100 })
return (
<div
onMouseMove={e => setState({ x: e.clientX - 20, y: e.clientY - 20 })}
style={{ left: state.x, top: state.y }}
>
<ChildComponent />
</div>
)
}
Chúng ta đã biết là khi component re-render, toàn bộ children của nó cũng được cập nhập, trong ví dụ trên nếu state
thay đổi, nếu component <ChildComponent />
quá nặng nó sẽ kéo performance rõ rệt.
Để giải quyết, thay vì dùng React.memo
, chúng ta sẽ thực hiện như sau
const MovingComponent = ({ children }) => {
return (
<div
onMouseMove={e => setState({ x: e.clientX - 20, y: e.clientY - 20 })}
style={{ left: state.x, top: state.y }}
>
{children}
</div>
)
}
// Sau đó
<MovingComponent>
<ChildComponent />
</MovingComponent>
Điều lạ kỳ đầu tiên, ủa nó vẫn là children mà?, thế quái nào nó lại không re-render?
Điều lạ kỳ thứ 2, nếu chúng ta truyền vào children như là một render function, ChildComponent
sẽ lại bị re-render, mặc dù nó ko phụ thuộc vào state
const MovingComponent = ({ children }) => {
...
return (
<div ...// y như cũ
>
{ children('something') }
</div>
)
}
// Sau đó
<MovingComponent>
// thậm chí không dùng prop truyền vô cho nó
{() => <ChildComponent />}
</MovingComponent>
Điều lạ kỳ thứ 3, giờ dùng React.memo
để chặn re-render
Trường hợp chỉ memo
MovingComponent, ChildComponent
vẫn re-render
const MovingComponentMemo = React.memo(MovingComponent)
const SomeOutsideComponent = () => {
// force render SomeOutsideComponent
const [state, setState] = useState();
return (
<MovingComponentMemo>
<ChildComponent />
</MovingComponentMemo>
)
}
Khi memo ChildComponent
không cần memo MovingComponent
, vấn đề được giải quyết
const ChildComponentMemo = React.memo(ChildComponent)
const SomeOutsideComponent = () => {
// force render SomeOutsideComponent
const [state, setState] = useState();
return (
<MovingComponent>
<ChildComponentMemo />
</MovingComponent>
)
}
Điều kỳ lạ thứ 4, sử dụng useCallback để chặn re-render cũng không thành công
const SomeOutsideComponent = () => {
const [state, setState] = useState();
// hy vọng càng thêm thất vọng
const child = useCallback(() => <ChildComponent />, []);
return (
<MovingComponent>
{child}
</MovingComponent>
)
}
Bạn có muốn tự tìm hiểu những ẩn số ở trên thì ngưng ở đây, còn muốn biết tại sao thì mời đọc tiếp
children
là cái gì trong React?
Cuối cùng const Parent = ({ children }) => (<>{children}</>)
<Parent>
<Child />
</Parent>
Khi chúng ta truyền children như thế, thật sự nó là gì? Nó là prop
, chúng ta rõ ràng viết như thế này vẫn được
<Parent children={<Child />} />
Parent có thể thay đổi, React xem children lúc này là prop
và nó ko phải component mà re-render, giải thích bí ẩn thứ nhất
Để giải thích được các bí ẩn ở trên, chúng ta cần nhớ vài điểm chính
React Element là gì?
Điểm quan trọng thứ 2 cần phải hiểu là chuyện gì diễn ra khi chúng ta viết
const child = <Child />
<Child />
được gọi là "Element", một cách viết hoa mỹ mà đằng sau nó là React.createElement trả về một object, và object này chứa mô tả mà sau đó được react-dom
dùng để render trên cây DOM
Nếu chúng ta viết
const Parent = () => {
const child = <Child />
// tương tự như
// const child = React.createElement(Child, null, null);
return <div />
}
Nó cũng giống như cost child = { }
, một giá trị nằm đó, ko có một hành động render nào xảy ra, render chỉ thực hiện khi chúng ta đặt nó trong return
const Parent = () => {
const child = <Child />;
return <div>{child}</div>;
};
Cập nhập Element
Element tạo ra từ bởi React.createElement
là một immutable object, cách duy nhất để cập nhập một Element là trigger việc re-render, một object tương tự được re-create
const Parent = () => {
const child = <Child />;
return <div>{child}</div>;
};
Khi component Parent
re-render, giá trị child
được tạo mới hoàn toàn, vốn không có vấn đề gì to tác, chỉ là một object.
Nếu ko muốn re-create object, chúng ta dùng đến memo, để cố định object này luôn
const ChildMemo = React.memo(Child);
const Parent = () => {
const child = <ChildMemo />;
return <div>{child}</div>;
};
// hoặc
const Parent = () => {
const child = useMemo(() => <Child />, []);
return <div>{child}</div>;
};
Lời giải thích
- Tại sao truyền component như prop thì không re-render
const MovingComponent = ({ children }) => {
const [state, setState] = useState();
return (
<div
// ...
style={{ left: state.x, top: state.y }}
>
{children}
</div>
);
};
const SomeOutsideComponent = () => {
return (
<MovingComponent>
<ChildComponent />
</MovingComponent>
)
}
"Children" <ChildComponent />
là một element được tạo ở SomeOutsideComponent
, khi MovingComponent
re-render, nó vẫn nhận cùng một object children, object này cũng không hề bị re-create và sẽ không re-render
- Nếu truyền children như render function, tại sao nó bị re-render
const MovingComponent = ({ children }) => {
const [state, setState] = useState();
return (
<div ///...
>
{children()}
</div>
);
};
const SomeOutsideComponent = () => {
return (
<MovingComponent>
{() => <ChildComponent />}
</MovingComponent>
)
}
Lúc này "children" là một function, chúng ta thực hiện execute để nó trả về object element, mỗi lần MovingComponent
re-render nó sẽ execute children function và trả về một object hoàn toàn mới
- Dùng memo ở "parent" component không chặn được re-render, và chỉ cần dùng memo với child component mà không cần dùng memo cho parent
const MovingComponentMemo = React.memo(MovingComponent);
const SomeOutsideComponent = () => {
const [state, setState] = useState();
return (
<MovingComponentMemo>
<ChildComponent />
</MovingComponentMemo>
)
}
Khi re-render SomeOutsideComponent
, chúng ta tạo mới hoàn toàn ChildComponent
trên mỗi lần re-render, React.memo
nó kiểm tra prop truyền vào cho MovingComponent
và lúc này nó đã là các object khác nhau trên mỗi lần render
Khi memo ChildComponent
const ChildComponentMemo = React.memo(ChildComponent);
const SomeOutsideComponent = () => {
const [state, setState] = useState();
return (
<MovingComponent>
<ChildComponentMemo />
</MovingComponent>
)
}
Trong trường hợp này, dù cho MovingComponent
xảy ra re-render, children khi đối chiếu sẽ hoàn toàn không khác, react-dom sẽ bỏ qua và không re-render lại những object không thay đổi
- Truyền children như một function, memo không còn hoạt động
const SomeOutsideComponent = () => {
const [state, setState] = useState();
const child = useCallback(() => <ChildComponent />, []);
return <MovingComponent>{child}</MovingComponent>;
// như này cho dễ hình dung
// return <MovingComponent children={child} />;
};
Khi SomeComponent
re-render, MovingComponent
cũng sẽ re-render, khi nó đó nó gọi tiếp function children, function được memoize nhưng giá trị nó return khác nhau ở mỗi lần execute
Hy vọng với bài viết này bạn làm chủ và giải thích được những bí ẩn đằng sau mỗi lần re-render
https://www.developerway.com/posts/react-elements-children-parents?utm_source=pocket_mylist
Những bí ẩn trong việc re-render trong React
Bạn có bao giờ bị bối rối như mình trong khi phát hiện component bị re-render, dù đã useMemo, useCallback, memo đủ kiểu. Hãy cùng tìm lời giải cho những ẩn số này.
const ChildComponent = () => {
console.log("ChildComponent re-render")
return <div>Child</div>
}
const MovingComponent = () => {
const [state, setState] = useState({ x: 100, y: 100 })
return (
<div
onMouseMove={e => setState({ x: e.clientX - 20, y: e.clientY - 20 })}
style={{ left: state.x, top: state.y }}
>
<ChildComponent />
</div>
)
}
Chúng ta đã biết là khi component re-render, toàn bộ children của nó cũng được cập nhập, trong ví dụ trên nếu state
thay đổi, nếu component <ChildComponent />
quá nặng nó sẽ kéo performance rõ rệt.
Để giải quyết, thay vì dùng React.memo
, chúng ta sẽ thực hiện như sau
const MovingComponent = ({ children }) => {
return (
<div
onMouseMove={e => setState({ x: e.clientX - 20, y: e.clientY - 20 })}
style={{ left: state.x, top: state.y }}
>
{children}
</div>
)
}
// Sau đó
<MovingComponent>
<ChildComponent />
</MovingComponent>
Điều lạ kỳ đầu tiên, ủa nó vẫn là children mà?, thế quái nào nó lại không re-render?
Điều lạ kỳ thứ 2, nếu chúng ta truyền vào children như là một render function, ChildComponent
sẽ lại bị re-render, mặc dù nó ko phụ thuộc vào state
const MovingComponent = ({ children }) => {
...
return (
<div ...// y như cũ
>
{ children('something') }
</div>
)
}
// Sau đó
<MovingComponent>
// thậm chí không dùng prop truyền vô cho nó
{() => <ChildComponent />}
</MovingComponent>
Điều lạ kỳ thứ 3, giờ dùng React.memo
để chặn re-render
Trường hợp chỉ memo
MovingComponent, ChildComponent
vẫn re-render
const MovingComponentMemo = React.memo(MovingComponent)
const SomeOutsideComponent = () => {
// force render SomeOutsideComponent
const [state, setState] = useState();
return (
<MovingComponentMemo>
<ChildComponent />
</MovingComponentMemo>
)
}
Khi memo ChildComponent
không cần memo MovingComponent
, vấn đề được giải quyết
const ChildComponentMemo = React.memo(ChildComponent)
const SomeOutsideComponent = () => {
// force render SomeOutsideComponent
const [state, setState] = useState();
return (
<MovingComponent>
<ChildComponentMemo />
</MovingComponent>
)
}
Điều kỳ lạ thứ 4, sử dụng useCallback để chặn re-render cũng không thành công
const SomeOutsideComponent = () => {
const [state, setState] = useState();
// hy vọng càng thêm thất vọng
const child = useCallback(() => <ChildComponent />, []);
return (
<MovingComponent>
{child}
</MovingComponent>
)
}
Bạn có muốn tự tìm hiểu những ẩn số ở trên thì ngưng ở đây, còn muốn biết tại sao thì mời đọc tiếp
children
là cái gì trong React?
Cuối cùng const Parent = ({ children }) => (<>{children}</>)
<Parent>
<Child />
</Parent>
Khi chúng ta truyền children như thế, thật sự nó là gì? Nó là prop
, chúng ta rõ ràng viết như thế này vẫn được
<Parent children={<Child />} />
Parent có thể thay đổi, React xem children lúc này là prop
và nó ko phải component mà re-render, giải thích bí ẩn thứ nhất
Để giải thích được các bí ẩn ở trên, chúng ta cần nhớ vài điểm chính
React Element là gì?
Điểm quan trọng thứ 2 cần phải hiểu là chuyện gì diễn ra khi chúng ta viết
const child = <Child />
<Child />
được gọi là "Element", một cách viết hoa mỹ mà đằng sau nó là React.createElement trả về một object, và object này chứa mô tả mà sau đó được react-dom
dùng để render trên cây DOM
Nếu chúng ta viết
const Parent = () => {
const child = <Child />
// tương tự như
// const child = React.createElement(Child, null, null);
return <div />
}
Nó cũng giống như cost child = { }
, một giá trị nằm đó, ko có một hành động render nào xảy ra, render chỉ thực hiện khi chúng ta đặt nó trong return
const Parent = () => {
const child = <Child />;
return <div>{child}</div>;
};
Cập nhập Element
Element tạo ra từ bởi React.createElement
là một immutable object, cách duy nhất để cập nhập một Element là trigger việc re-render, một object tương tự được re-create
const Parent = () => {
const child = <Child />;
return <div>{child}</div>;
};
Khi component Parent
re-render, giá trị child
được tạo mới hoàn toàn, vốn không có vấn đề gì to tác, chỉ là một object.
Nếu ko muốn re-create object, chúng ta dùng đến memo, để cố định object này luôn
const ChildMemo = React.memo(Child);
const Parent = () => {
const child = <ChildMemo />;
return <div>{child}</div>;
};
// hoặc
const Parent = () => {
const child = useMemo(() => <Child />, []);
return <div>{child}</div>;
};
Lời giải thích
- Tại sao truyền component như prop thì không re-render
const MovingComponent = ({ children }) => {
const [state, setState] = useState();
return (
<div
// ...
style={{ left: state.x, top: state.y }}
>
{children}
</div>
);
};
const SomeOutsideComponent = () => {
return (
<MovingComponent>
<ChildComponent />
</MovingComponent>
)
}
"Children" <ChildComponent />
là một element được tạo ở SomeOutsideComponent
, khi MovingComponent
re-render, nó vẫn nhận cùng một object children, object này cũng không hề bị re-create và sẽ không re-render
- Nếu truyền children như render function, tại sao nó bị re-render
const MovingComponent = ({ children }) => {
const [state, setState] = useState();
return (
<div ///...
>
{children()}
</div>
);
};
const SomeOutsideComponent = () => {
return (
<MovingComponent>
{() => <ChildComponent />}
</MovingComponent>
)
}
Lúc này "children" là một function, chúng ta thực hiện execute để nó trả về object element, mỗi lần MovingComponent
re-render nó sẽ execute children function và trả về một object hoàn toàn mới
- Dùng memo ở "parent" component không chặn được re-render, và chỉ cần dùng memo với child component mà không cần dùng memo cho parent
const MovingComponentMemo = React.memo(MovingComponent);
const SomeOutsideComponent = () => {
const [state, setState] = useState();
return (
<MovingComponentMemo>
<ChildComponent />
</MovingComponentMemo>
)
}
Khi re-render SomeOutsideComponent
, chúng ta tạo mới hoàn toàn ChildComponent
trên mỗi lần re-render, React.memo
nó kiểm tra prop truyền vào cho MovingComponent
và lúc này nó đã là các object khác nhau trên mỗi lần render
Khi memo ChildComponent
const ChildComponentMemo = React.memo(ChildComponent);
const SomeOutsideComponent = () => {
const [state, setState] = useState();
return (
<MovingComponent>
<ChildComponentMemo />
</MovingComponent>
)
}
Trong trường hợp này, dù cho MovingComponent
xảy ra re-render, children khi đối chiếu sẽ hoàn toàn không khác, react-dom sẽ bỏ qua và không re-render lại những object không thay đổi
- Truyền children như một function, memo không còn hoạt động
const SomeOutsideComponent = () => {
const [state, setState] = useState();
const child = useCallback(() => <ChildComponent />, []);
return <MovingComponent>{child}</MovingComponent>;
// như này cho dễ hình dung
// return <MovingComponent children={child} />;
};
Khi SomeComponent
re-render, MovingComponent
cũng sẽ re-render, khi nó đó nó gọi tiếp function children, function được memoize nhưng giá trị nó return khác nhau ở mỗi lần execute
Hy vọng với bài viết này bạn làm chủ và giải thích được những bí ẩn đằng sau mỗi lần re-render
The mystery of React Element, children, parents and re-renders
Initializing...