Hiểu về cách thức hoạt động của Reactjs Hooks p2
1. Virtual DOM là gì?
Giống như DOM thực, DOM ảo là một nodes tree liệt kê các phần tử, các thuộc tính và nội dung của chúng dưới dạng các đối tượng và thuộc tính. Phương thức render()
trong ReactDOM tạo một nodes tree từ các thành phần React và cập nhật tree này để phản ứng với các thay đổi trong mô hình dữ liệu do các hành động gây ra.
Bất cứ khi nào data thay đổi, toàn bộ UI được re-rendered lại đầu tiền trong DOM ảo. Lúc này DOM ảo sẽ tiến hành tính toán sự sai khác giữa DOM ảo lần trước so với hiện tại. Quá trình tính toán kết thúc, DOM thực sẽ chỉ cập nhật những thay đổi chứ không cập nhật hoàn toàn. Việc cập nhật DOM ảo diễn ra rất nhanh so với trình duyệt thực sự re-render. Do đó, hiệu suất được cải thiện.
2. Virtual DOM hoạt động như thế nào?
Virtual DOM hoạt động thông qua 3 bước sau :
- Bất kỳ khi nào có sự thay đổi dữ liệu trong component, toàn bộ UI sẽ re-render lại bên trong Virtual DOM.
- Sau khi thấy được sự sai khác giữa DOM lần trước với DOM mới, nó sẽ tiến hành tính toán.
- Sau khi quá trình tính toán sự thay đổi hoàn tất, DOM thực sẽ được update với những thứ thực sự thay đổi, còn lại vẫn giữ nguyên.
3. controlled component là gì?
controlled component
là component kiểm soát các phần tử nhập dữ liệu input element
bên trong form
được gọi là controlled component
, mỗi khi thay đổi state
sẽ liên kết đến các hàm xử lý thay đổi.
handleChange(event) { this.setState({value: event.target.value.toUpperCase()}) }
4. uncontrolled component là gì?
Uncontrolled component
là component lưu trữ các state riêng bên trong nó. Khi cần truy vấn đến DOM sử dụng ref
để tìm kiếm giá trị hiện tại, nó khá giống với HTML truyền thống.
Trong component UserProfile
bên dưới, đầu vào tên được truy cập bằng cách sử dụng ref
.
class UserProfile extends React.Component { constructor(props) { super(props) this.handleSubmit = this.handleSubmit.bind(this) this.input = React.createRef() } handleSubmit(event) { alert('A name was submitted: ' + this.input.current.value) event.preventDefault() } render() { return ( <form onSubmit={this.handleSubmit}> <label> {'Name:'} <input type="text" ref={this.input} /> </label> <input type="submit" value="Submit" /> </form> ); } }
Trong hầu hết các trường hợp, bạn nên sử dụng các controlled component để triển khai các form.
5. Higher-Order Components là gì?
Higher-order Component (HOC) là một hàm lấy một component và trả về một component mới.
Chúng ta gọi HOC là pure component
vì chúng có thể chấp nhận bất kỳ thành phần con nào được cung cấp động nhưng chúng sẽ không sửa đổi hoặc sao chép bất kỳ hành vi nào từ các component đầu vào của chúng.
const EnhancedComponent = higherOrderComponent(WrappedComponent)
HOC có thể được sử dụng trong nhiều trường hợp :
- Tái sử dụng code
- Render hijacking
- Thao tác với state
- thao tác với các Props.
6. Làm thế nào để tạo các props ủy quyền cho HOC?
Bạn có thể thêm/ chỉnh sửa các props được chuyển đến component bằng cách sử dụng mẫu proxy props như sau:
function HOC(WrappedComponent){ return class Test extends Component{ render() { const newProps = { title: 'New Header', footer: false, showFeatureX: false, showFeatureY: true } return <WrappedComponent {...this.props} {...newProps} /> } } }
7. Trong một dự án React, chúng ta thêm tham chiếu vào 2 tệp. Một là react.js và một cái khác là react-dom.js. Tại sao chúng ta lại có bao gồm 2 tệp, thay vì một?
Thư viện component React được sử dụng trong các trang web và cũng để tạo ứng dụng di động bằng React Native. Tệp React.js là một tệp nhỏ thực hiện công việc tạo các component. Do đó, nó được sử dụng trong cả web và các dự án React-Native. Trong web, các component sau đó được hiển thị trong trình duyệt bằng cách sử dụng react-dom.js. Vì vậy, 2 tệp được tách ra để sử dụng lại.
8. chuyển JSX thành Pure Javascript
Giả sử ta có đoạn code sau:
const content = ( <div> <h1>Backbencher</h1> </div> );
Viết một đoạn code pure Javascript sau khi JSX chuyển sang JS
const content = React.createElement( "div", {}, React.createElement("h1", {}, "Backbencher") )
Phương thức React.createElement()
chấp nhận 3 tham số :
createElement(tag, attributes, children)
- Tham số đầu tiên là thẻ hoặc một component được render.
- Tham số thứ 2 chập nhận một object, các cặp key/value của object này là thuộc tính của thẻ
- Tham số thứ 3 là một string hoặc một component khác được chứa trong thẻ hiện tại hoặc component hiện tại.
React.createElement("div", {id: "hello"}, "Backbencher")
Sẽ trở thành như sau :
<div id="hello">Backbencher</div>
9. JSX là gì? Những thuận lợi khi sử dụng nó?
JSX là cú pháp mở rộng trong Javascript. Nó được tạo ra để viết React component một cách dễ dàng. Nếu như không có JSX, rất khó để viết được những components React lớn bằng pure Javascript
10. Pure function là gì?
Một hàm thuần túy (pure function) không thay đổi đầu vào của nó. Nó luôn trả về cùng một giá trị cho cùng một đầu vào. Trong React, một component cần một hàm thuần túy đối với các props của nó. Điều đó có nghĩa là đối với một số props
cụ thể, thành phần được rendered sẽ luôn giống nhau.
11. Xử lý sự kiện trong React component
Giả sử ta có class component sau :
class Banner extends React.Component { state = { text: "" } incrementCount = () => { this.state.text = "Backbencher"; } render() { return ( <div> <button onClick={this.incrementCount}>Click</button> <h1>{this.state.text}</h1> </div> ) } }
Ở đây khi button được click, nó sẽ hiển thị văn bản "Backbencher". Nhưng điều đó không xảy ra. Nguyên nhân là gì?
state
được updated không đúng cách, để update một giá trị nào đó trong state
ta cần sử dụng setState()
, khi đó UI mới được re-render lại.
The incrementCount
cần được thay
incrementCount = () => { this.setState({ text: "Backbencher" }) }
12. setState()
đồng bộ hay bất đồng bộ ?
Phương thức setState()
là bất đồng bộ, nó sẽ trả về một callback function ở tham số thứ 2.
Giả sử ta có đoạn code sau :
this.setState({ counter: this.state.counter + this.props.increment, });
What could be the reason? How can we fix it?
Since setState()
is asynchronous, setting new state based on previous state can go wrong sometimes. In such scenarios, we can use callback function syntax to set state
Vì setState()
bất đồng bộ nên việc thiết lập một state mới dựa trên previous state có thể dẫn đến sai trong một số tình huống xung đột, thay vào đó sử dụng cú pháp callback function để setState()
this.setState((prevState, props) => { return { counter: prevState.counter + props.increment } })
13. React Hook là gì?
Hook là một tính năng mới xuất hiện từ React 16.8. Nó cho phép chúng ta sử dụng tất cả các tính năng của React dưới dạng function component thay vì phải viết dưới dạng class component. Với Hook chúng ta có thể lưu trữ state
nhờ vào useState()
.
14. Tại sao React hook lại được giới thiệu?
Lý do đầu tiên để hook được giới thiệu là vì sự phức tạp trong qúa trình xử lý với từ khóa this
bên trong class component. Nếu không xử lý chính xác, this
sẽ lấy giá trị khác không mong muốn. Điều đó sẽ dẫn đến việc ngắt các dòng như this.setState()
và các trình xử lý sự kiện khác. Bằng cách sử dụng hook, chúng ta có thể tránh được sự phức tạp đó khi làm việc với các functional component
Các class component không được tối ưu và cũng làm cho việc reload lại không đáng tin cậy. Đó là một nguồn cảm hứng khác để mang đến hook.
Một lý do khác là, chưa có cách cụ thể để có thể tái sử dụng lại các component. Mặc dù HOC và render các props giải quyết được vấn đề này nhưng class component không thể thực hiện được. Các hook cho phép chia sẻ các state
logic mà không cần thay đổi cấu trúc phân cấp của component.
Lý do thứ tư là, trong một class component phức tạp, các code liên quan nằm rải rác trong các phương thức vòng đời khác nhau. VD, khi muốn fetch data, chúng ta cần truy cập componentDidMount()
hoặc componentDidUpdate()
. Trong một số trường hợp xử lý event listener hoặc subscribe một sự kiện nào đó, chúng ta sử dụng componentDidMount()
để bắt sự kiện, và componentWillUnmount()
để hủy sự kiện đó. Hook thay vì phải xử lý quá nhiều như vậy, chỉ cần gói chúng trong useEffect()
.
15. useState hoạt động như thế nào? Các tham số số được chấp nhận bởi hook này là gì và hook trả về những gì?
useState
trong hook là một hàm được dùng để lưu trữ giá trị của state trong functional component. Nó chấp nhận một tham số là giá trị ban đầu của trạng thái. Nó trả về một mảng có 2 phần tử. Phần tử đầu tiên là giá trị hiện tại của trạng thái. Yếu tố thứ hai là chức năng cập nhật trạng thái.
Đầu tiên chúng ta cần import useState
trong hook:
import React, {useState} from "react";
Sau đó, chúng ta sử dụng useState
như sau :
const [currentStateValue, functionToUpdateState] = useState(initialStateValue);
16. Mục đích useEffect trong hook là gì?
useEffect
hook cho phép chúng ta thực hiện các các thay đổi bên trong các component. Nó giúp chúng ta tránh code dư thừa trong các phương thức vòng đời khác nhau của một class component. Nó giúp nhóm các code được gắn kết với nhau thay vì rời rạc như trong class component.
VD : Đây là một class component có thể in Boom
ra console bất cứ khi nào nó được gắn hoặc cập nhật.
export class Banner extends Component { state = { count: 0, }; updateState = () => { this.setState({ count: this.state.count + 1, }); }; componentDidMount() { console.log("Boom"); } componentDidUpdate() { console.log("Boom"); } render() { return ( <div> <button onClick={this.updateState}>State: {this.state.count}</button> </div> ); } }
componentDidMount()
và componentDidUpdate()
là các phương thức vòng đời. Các side effect như vậy có thể được thực hiện bằng hook useEffect
. useEffect hook là một hàm chấp nhận một callback function và nó được gọi mỗi khi render xảy ra.
import React, { useState, useEffect } from "react"; function Banner() { const [count, setCount] = useState(0); useEffect(() => { console.log("Boom"); }); const updateState = () => { setCount(count + 1); }; return ( <div> <button onClick={updateState}>State: {count}</button> </div> ); }
17. Kỹ thuật render props trong React là gì?
Render Props đề cập đến một kỹ thuật chia sẻ mãcodegiữa các component trong React bằng cách sử dụng một prop có giá trị là một hàm.
Ưu điểm chính là khả năng tái sử dụng mã. Vd như : Có một component mega-menu được tạo bởi nhà phát triển thứ 3. Component này sẽ render các phần tử trong menu và sub-menu theo định dạng HTML.
<ul class="menu"> <li>Menu Item 1 <ul class="sub-menu"> <li>Sub menu 1</li> <li>Sub menu 2</li> </ul> </li> </ul>
Nếu người dùng muốn thêm một số nội dung bên dưới mỗi sub-menu, họ cần chỉnh sửa code. Việc sử dụng Render props từ các thư viện có thể cho phép người dùng linh hoạt thêm nội dung tùy chỉnh bên dưới sub-menu.
Ở đây chúng ta có hai thành phần <GithubSearchCount />
và <GithubSearchResult />
. Cả hai thành phần đều thực hiện tìm kiếm người dùng Github. <GithubSearchCount />
hiển thị số lượng kết quả tìm kiếm, trong đó <GithubSearchResult />
hiển thị các kết quả tìm kiếm dưới dạng danh sách.
/****** GithubSearchCount ******/ import React, { Component } from "react"; export class GithubSearchCount extends Component { state = { result: [], }; componentDidMount() { fetch("https://api.github.com/search/users?q=joby") .then((response) => response.json()) .then((data) => { console.log(data); this.setState((prevState) => { return { result: data, }; }); }); } render() { return <h1>Total search count: {this.state.result?.total_count}</h1>; } } export default GithubSearchCount;
/****** GithubSearchResult ******/ import React, { Component } from "react"; export class GithubSearchResult extends Component { state = { result: [], }; componentDidMount() { fetch("https://api.github.com/search/users?q=joby") .then((response) => response.json()) .then((data) => { console.log(data); this.setState((prevState) => { return { result: data, }; }); }); } render() { return ( <div> <ul> {this.state.result?.items?.map((item) => ( <li>{item.login}</li> ))} </ul> </div> ); } } export default GithubSearchResult;
Trong cả hai thành phần, chúng ta đang viết lại code để fetch API Github. Làm cách nào chúng ta có thể sử dụng Render props để sử dụng lại code API fetch?
Điều đầu tiên cần làm là đưa đoạn code sang một component React mới, chẳng hạn như <GithubSearch />
. Sau đó, quyết định về những gì sẽ hiển thị trong <GithubSearch />
cần được thực hiện động dựa trên các đạo cụ được chuyển qua.
//GithubSearch.jsx import React, { Component } from "react"; export class GithubSearch extends Component { state = { result: [], }; componentDidMount() { fetch("https://api.github.com/search/users?q=joby") .then((response) => response.json()) .then((data) => { console.log(data); this.setState((prevState) => { return { result: data, }; }); }); } render() { return <>{this.props.render(this.state.result)}</>; } } export default GithubSearch;
Tiếp theo, chúng ta chuẩn bị <GithubSearchResult />
và <GithubSearchCount />
để nhận kết quả tìm kiếm từ các props.
Đây là component GithubSearchCount
được update.
export class GithubSearchCount extends Component { render() { return <h1>Total search count: {this.props.result?.total_count}</h1>; } }
Đây là component GithubSearchResult
được update.
export class GithubSearchResult extends Component {
render() {
return (
<div>
<ul>
{this.props.result?.items?.map((item) => (
<li>{item.login}</li>
))}
</ul>
</div>
);
}
}
Cuối cùng, chúng ta sử dụng component GithubSearch
như sau:
<GithubSearch render={(result) => <GithubSearchCount result={result} />} /> <GithubSearch render={(result) => <GithubSearchResult result={result} />} />