Nội dung bài học
Các component trong React có các thuộc tính tương tự các thuộc tính của các phần tử HTML. Cú pháp:
<Tên_Component Thuộc_tính_1 = giá_trị_1 Thuộc_tính_2 = giá_trị_2 ... />
Ví dụ: Các thuộc tính brand và model của component Car
<Car brand="Ford" model=" Mustang" />
Để sử dụng các thuộc tính, component sử dụng các đối tượng props và state.
Các thuộc tính của component có thể được kết xuất đến người dùng thông qua đối tượng props theo cú pháp
this.props.Thuộc_tính.
Ví dụ component Car có hai thuộc tính là brand và model và để render giá trị các thuộc tính này chúng ta dùng đối tượng props như sau:
class Car extends React.Component {
render() {
return
<h2>I am a {this.props.brand} and {this.props.model}!</h2>;
}
}
const myComponent =
<Car brand="Ford" model="Mustang" />;
ReactDOM.render(myComponent, document.getElementById('root'));
Có thể thấy props tương tự các đối số trong hàm. Nhờ props, dữ liệu có thể được chuyển đổi qua lại giữa các component. Ví dụ gửi dữ liệu các thuộc tính brand và model từ Garage đến Car:
class Car extends React.Component {
render() {
return <h2>I am a {this.props.brand} and {this.props.model}!</h2>;
}
}
class Garage extends React.Component {
render() {
return (
<div>
<h1>Who lives in my garage?</h1>
<Car brand="Ford" model="Mustang" />
</div>
);
}
}
ReactDOM.render(
<Garage />, document.getElementById('root'));
Nếu dữ liệu thuộc tính một component có cấu trúc phức tạp thay vì một chuỗi đơn giản như Ford hay Mustang chúng ta có thể dùng biến. Ví dụ khai báo biến carinfo kiểu object chứa nhiều thuộc tính:
class Car extends React.Component {
render() {
return
<h2>I am a {this.props.info.brand} and
{this.props.info.model}!</h2>;
}
}
const carinfo = { brand: "Ford", model: "Mustang" };
const myComponent =
<Car info = {carinfo} />;
ReactDOM.render(myComponent, document.getElementById('root'));
Nếu component có chứa hàm constructor thì props nên được chuyển đến constructor và React.Component thông qua hàm super():
class Car extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h2>I am a {this.props.info.brand} and
{this.props.info.model}!</h2>;
}
}
Lý do cho việc này sẽ được giải thích trong đối tượng state. Tham khảo tại stackoverflow.com.
Đối tượng state là nơi chúng ta lưu trữ các giá trị thuộc tính của component. Đối tượng state luôn được khởi tạo trong hàm constructor của component. Ví dụ đối tượng state lưu trữ thuộc tính brand:
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {brand: "Ford"};
}
render() {
return <h2>I am a Car.</h2>;
}
}
Hay lưu trữ dữ liệu phức tạp hơn (tức nhiều thuộc tính):
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang",
};
}
render() {
return <h2>I am a Car.</h2>;
}
}
Sử dụng state theo cú pháp this.state.Thuộc_tính:
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang",
};
}
render() {
return (
<div>
<h2>I am a Car.</h2>
<p>My brand is {this.state.brand}
and my model is {this.state.model}</p>
</div>
);
}
}
const myComponent =
<Car />;
ReactDOM.render(myComponent, document.getElementById('root'));
Lưu ý rằng, với state khi component được sử dụng sẽ không cần chuyển tên thuộc tính như khi sử dụng đối tượng props. Một điều khác phân biệt props và state đó là props chỉ đọc (read only) trong khi state có thể thay đổi bằng cách dùng hàm setState()
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang"
};
}
changeCar = () => {
this.setState({ brand: "Toyota", model: "Camry" });
}
render() {
return (
<div>
<h2>I am a Car.</h2>
<p>My brand is {this.state.brand} and my model is
{this.state.model}</p>
<button onClick={this.changeCar}>
Change Car
</button>
</div>
);
}
}
const myComponent =
<Car />;
ReactDOM.render(myComponent, document.getElementById('root'));
Thông thường, khi một dữ liệu thay đổi nó sẽ ảnh hưởng tới nhiều component cùng lúc. State được khuyến khích chia sẻ ở component cha của chúng.
Xem giải thích và ví dụ minh họa tại https://vi.reactjs.org/docs/lifting-state-up.html
Cuối cùng, hãy tóm tắt lại và xem sự khác biệt chính giữa props và state:
Tham chiếu, hoặc ref, là một tính năng cho phép các thành phần React tương tác với các phần tử con. Trường hợp sử dụng phổ biến nhất cho các refs là tương tác với các phần tử UI có đọc đầu vào từ người dùng. Hãy xem xét phần tử form HTML. Những phần tử này được kết xuất đến trình duyệt nhưng người dùng có thể tương tác với chúng và các form cần phản hồi một cách thích hợp.
Hãy xem xét component FancyButton hiển thị phần tử DOM của button gốc:
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}
Các React components ẩn chi tiết triển khai của chúng, bao gồm cả đầu ra được hiển thị của chúng. Các components khác sử dụng FancyButton thường sẽ không cần lấy tham chiếu đến phần tử DOM của button bên trong. Điều này là tốt vì nó ngăn các components dựa vào cấu trúc DOM của nhau quá nhiều.
Trong đoạn mã dưới đây, FancyButton sử dụng React.forwardRef để lấy ref được chuyển đến nó, sau đó chuyển tiếp nó đến DOM button mà nó hiển thị:
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// Bây giờ có thể nhận được ref trực tiếp đến nút DOM:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Bằng cách này, các components sử dụng FancyButton có thể nhận được tham chiếu đến DOM button bên dưới và truy cập nó nếu cần - giống như nếu chúng sử dụng trực tiếp DOM button.
Dưới đây là giải thích từng bước về những gì xảy ra trong ví dụ trên:
Tham khảo thêm về ref tại reactjs.org.
Mỗi component trong React có một vòng đời (lifecycle) mà chúng ta có thể theo dõi và xử lý. Vòng đời này gồm 3 giai đoạn:
Trong giai đoạn này, React có 4 phương thức cơ bản là constructor(), getDerivedStateFromProps(), render() và componentDidMount(). Trong đó phương thức render() phải luôn luôn được gọi và các phương thức khác là tùy chọn. Trong các bài trước chúng ta đã làm quen với render() và constructor():
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {brand: "Ford"};
}
render() {
return
<h2>I am a Car.</h2>;
}
}
Trong component, phương thức constructor được gọi đầu tiên và phương thức super() cũng phải được gọi đầu tiên trong constructor. Đối tượng state cũng phải được khởi tạo trong constructor và đối tượng props cũng là tham số của constructor và super.
Phương thức render() dùng để kết xuất các component đến trình duyệt hay chính xác hơn là kết xuất các phần tử HTML đến cây DOM.
Phương thức getDerivedStateFromProps() được gọi trước khi các phần tử được kết xuất đến cây DOM và là nơi khởi tạo đối tượng state dựa trên đối tượng props. Nó nhận state như đối số và sẽ trả về một đối tượng với sự thay đổi đối tượng state. Ví dụ khởi tạo các thuộc tính brand và model từ state đến các thuộc tính newbrand và newmodel của props.
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang",
};
}
static getDerivedStateFromProps(props, state) {
return { brand: props.newbrand, model: props.newmodel };
}
render() {
return (
<div>
<h2>I am a Car.</h2>
<p>My brand is {this.state.brand} and my model is
{this.state.model}</p>
</div>
);
}
}
const myComponent = <Car newbrand="Toyota" newmodel="Camry" />;
ReactDOM.render(myComponent, document.getElementById('root'));
Ngược với getDerivedStateFromProps, phương thức componentDidMount() được gọi sau khi component được kết xuất đến DOM. Điều này cũng có nghĩa phương thức này là nơi dùng các lệnh yêu cầu component khi nó đã được đặt trên DOM. Ví dụ Car sẽ được kết xuất và thay đổi các thuộc tính sau 3 giây:
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang",
};
}
componentDidMount() {
setTimeout(() => {
this.setState({ brand: "Toyota", model: "Camry" });
}, 3000);
}
render() {
return (
<div>
<h2>I am a Car.</h2>
<p>My brand is {this.state.brand} and my model is
{this.state.model}</p>
</div>
);
}
}
const myComponent = <Car />;
ReactDOM.render(myComponent, document.getElementById('root'));
Khi component được kết xuất đến DOM có thể sẽ có sự thay đổi liên quan đến state hay props và đây là giai đoạn updating. Trong giai đoạn này có các phương thức getDerivedStateFromProps(), shouldComponentUpdate(), render(), getSnapshotBeforeUpdate() và componentDidUpdate(). Trong đó, render() là phương thức được gọi bắt buộc, các phương thức khác tùy chọn.
Khi xuất hiện cập nhật thông tin đến component, phương thức getDerivedStateFromProps() luôn được ưu tiên số 1 (nếu chúng ta định nghĩa). Xét trở lại ví dụ
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang",
};
}
changeCar = () => {
this.setState({ brand: "Toyota", model: "Camry" });
}
render() {
return (
<div >
<h2 >I am a Car. </h2 >
<p >My brand is {this.state.brand} and my model is
{this.state.model} </p >
<button onClick={this.changeCar} >
Change Car
</button >
</div >
);
}
}
const myComponent =
<Car newbrand="Honda" newmodel="Civic" / >
ReactDOM.render(myComponent, document.getElementById('root'));
Kết quả:
Nếu nhấn nút Change Car
Hai thuộc tính newbrand và newmodel không được liên quan gì vì chúng thuộc quyền quản lý của props. Bây giờ thêm phương thức getDerivedStateFromProps() đến component Car:
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang",
};
}
static getDerivedStateFromProps(props, state) {
return {brand: props.newbrand, model:props.newmodel};
}
changeCar = () => {
this.setState({ brand: "Toyota", model: "Camry" });
}
render() {
return (
<div>
<h2>I am a Car.</h2>
<p>My brand is {this.state.brand} and my model is
{this.state.model}</p>
<button onClick={this.changeCar}>
Change Car
</button>
</div>
);
}
}
const myComponent =
<Car newbrand="Honda" newmodel="Civic" />;
ReactDOM.render(myComponent, document.getElementById('root'));
Kết quả:
Dù chúng ta nhấn nút Change Car cũng không thay đổi gì. Phương thức khác trong giai đoạn Updating là shouldComponentUpdate() quyết định liệu component có tiếp tục cập nhật hay không. Giá trị trả về của phương thức này là true (tiếp tục cập nhật) hay false (ngừng cập nhật). Xét lại ví dụ component Car:
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang",
};
}
changeCar = () => {
this.setState({ brand: "Toyota", model: "Camry" });
}
render() {
return (
<div>
<h2>I am a Car.</h2>
<p>My brand is {this.state.brand} and my model is
{this.state.model}</p>
<button onClick={this.changeCar}>
Change Car
</button>
</div>
);
}
}
const myComponent = <Car />;
ReactDOM.render(myComponent, document.getElementById('root'));
Thuộc tính của Car sẽ thay đổi khi nhấn nút Change Car. Bây giờ chúng ta thêm phương thức shouldComponentUpdate()
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang",
};
}
shouldComponentUpdate() {
return false;
}
changeCar = () => {
this.setState({ brand: "Toyota", model: "Camry" });
}
Vì trả về false nên Car sẽ không thay đổi khi nhấn nút Change Car. Tuy nhiên, nếu chúng ta thay đổi giá trị trả về thành true thì mọi thứ sẽ trở lại bình thường (nghĩa là được cập nhật). Tại giai đoạn cập nhật chúng ta có thể thay đổi thông tin về component và chúng ta cũng muốn biết thông tin trước và sau khi thay đổi component. Phương thức getSnapshotBeforeUpdate() sẽ cung cấp các thông tin trước khi cập nhật và phương thức componentDidUpdate() sẽ cung cấp thông tin sau khi cập nhật. Xét một ví dụ diễn ra quá trình cập nhật component Car và các thông tin trước và sau cập nhật được trả về. Xét ví dụ
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang",
};
}
getSnapshotBeforeUpdate(prevProps, prevState) {
document.getElementById("div1").innerHTML = "Before the update, the brand was " +
prevState.brand + " and model was " + prevState.model;
}
componentDidUpdate() {
document.getElementById("div2").innerHTML = "The updated car is " + this.state.brand + "
brand and " + this.state.model + " model";
}
changeCar = () => {
this.setState({ brand: "Toyota", model: "Camry" });
}
render() {
return (
<div>
<h2>I am a Car.</h2>
<p>My brand is {this.state.brand} and my model is
{this.state.model}</p>
<button onClick={this.changeCar}>
Change Car
</button>
<div id="div1"></div>
<div id="div2"></div>
</div>
);
}
}
const myComponent =
<Car />;
ReactDOM.render(myComponent, document.getElementById('root'));
Thực thi
Nhấn Change Car
Đây là giai đoạn khi một component được xóa khỏi DOM. Tại giai đoạn này chỉ có một phương thức là componentWillUnmount() được gọi. Tham khảo ví dụ tại https://www.w3schools.com/react/showreact.asp?filename=demo2_react_lifecycle_componentwillunmount
Giống như HTML, các component React cũng phản ứng đến các hành động người dùng thông qua các sự kiện (events). Chúng ta đã dùng sự kiện onClick cho button và trong bài này chúng ta sẽ tìm hiểu chi tiết hơn.
Sự kiện là các hành động tương tác của người dùng (user) đến giao diện ứng dụng như nhấn chuột trái đến button, nhập thông tin,...
Ứng dụng sẽ đưa ra phản ứng tương ứng với các hành động của người dùng gọi là xử lý sự kiện. Hiểu một cách đơn giản, một trình xử lý sự kiện là hàm hay phương thức thực hiện chức năng nào đó tương ứng với một sự kiện nào đó của người dùng.
Trong HTML, sự kiện nhấn chuột trái của button được viết như sau:
<button onclick="changeCar()">Change Car!</button>
Với onclick phản ánh sự kiện nhấn chuột trái và changeCar() là hàm (hay phương thức) thực thi khi người dùng nhấn chuột trái đến button. Hàm changeCar() còn được gọi là trình xử lý sự kiện onclick. Trong HTML các sự kiện được viết thường như onclick, onchange, onmouseover,...
Các sự kiện trong React được viết theo kiểu camelCase tức là onClick, onChange, onMouseOver,…(xem danh sách chi tiết tại https://reactjs.org/docs/events.html). Xem lại ví dụ:
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang"
};
}
changeCar = () => {
this.setState({ brand: "Toyota", model: "Camry" });
}
render() {
return (
<div>
<h2>I am a Car.</h2>
<p>My brand is {this.state.brand} and my model is
{this.state.model}</p>
<button onClick={this.changeCar}>
Change Car
</button>
</div>
);
}
}
Có một số khác biệt so với HTML chúng ta cần lưu ý:
Từ khóa this trong React chỉ component chứa hàm sử dụng. Ví dụ this trong ví dụ trên chỉ component Car chứa hàm changeCar. Đây cũng chính là lý do chúng ta phải dùng hàm mũi tên để định nghĩa changeCar thay vì định nghĩa hàm kiểu thông thường. Với hàm mũi tên, từ khóa this luôn thể hiện đối tượng định nghĩa hàm đó. Để hiểu hơn, giả sử chúng ta định nghĩa changeCar theo kiểu thông thường như sau:
changeCar() {
this.setState({ brand: "Toyota", model: "Camry" });
}
Kết quả sẽ xuất hiện lỗi vì React không xác định được this chỉ đối tượng nào:
Để khắc phục lỗi này mà không dùng hàm mũi tên chúng ta dùng hàm bind() để kết nối hàm với đối tượng định nghĩa nó. Ví dụ chúng ta dùng hàm bind() trong hàm constructor của component Car như sau:
constructor(props) {
super(props);
this.state = {
brand: "Ford",
model: "Mustang"
};
this.changeCar = this.changeCar.bind(this)
}
Với JS hiện đại chúng ta nên làm quen với hàm mũi tên vì những tiện ích nó mang lại. Để hiểu hơn về hàm mũi tên trong JS bạn tham khảo tại https://ngocminhtran.com/2020/02/26/javascript-hien-dai-phan-1/ Trong trường hợp hàm chứa các tham số ví dụ changeCar có thể định nghĩa như sau:
changeCar = (b, m) => {
this.setState({ brand: b, model: m });
}
Khi đó, tại sự kiện onClick chúng ta viết:
<button onClick={() => this.changeCar("Toyota", "Camry")}>
</button>
React cũng có thể cho chúng ta biết chính xác đối tượng sự kiện chúng ta đang làm việc. Có thể tham khảo tại W3Schools.
Trong React để kết xuất các component theo điều kiện, chúng ta thực hiện bằng nhiều cách khác nhau.
Có thể dùng lệnh if trong javaScript để truy xuất các component. Ví dụ chúng ta có 2 component sau:
function Fail() {
return <h1>BẠN ĐÃ RỚT RỒI!</h1>;
}
function Pass() {
return <h1>CHÚC MỪNG BẠN ĐÃ ĐẬU!</h1>;
}
Chúng ta tạo ra component Exam để kết xuất các component Fail hay Pass dùng lệnh if như sau:
function Exam(props) {
const isExam = props.isExam;
if (isExam) {
return <Pass />;
}
return <Fail />;
}
Kiểm tra kết xuất Exam đến DOM
ReactDOM.render(
<Exam isExam={false} />, document.getElementById("root"));
Kết quả
Kiểm tra kết xuất Exam đến DOM
ReactDOM.render(
<Exam isExam={true} />, document.getElementById("root"));
Kết quả
Trong JS, toán tử ba ngôi ?: là một biến thể của lệnh if else. Các component cũng có thể được kết xuất dùng toán tử ba ngôi ? : Ví dụ component Exam có thể được viết lại như sau:
function Exam(props) {
const isExam = props.isExam;
return (
<>
{isExam ? <Pass /> : <Fail />}
</>
);
}
Lưu ý rằng biểu thức dùng toán tử ba ngôi ?: là toán tử JS nên được đặt trong cặp ngoặc móc {}.
Các component cũng có thể được kết xuất theo điều kiện trong React dùng toán tử &&. Ví dụ
function Garage(props) {
const cars = props.cars;
return (
<>
<h1>Sưu tập xe</h1>
{cars.length > 0 &&
<h2>
Bạn có {cars.length} xe trong bộ sưu tập.
</h2>
}
</>
);
}
const cars = ['Ford', 'BMW', 'Audi'];
ReactDOM.render(
<Garage cars={cars} />, document.getElementById("root"));
Lưu ý rằng biểu thức dùng toán tử && là toán tử JS nên được đặt trong cặp ngoặc móc {}. Trong ví dụ trên, nếu cars.length hay số phần tử mảng cars lớn hơn 0 thì sẽ kết xuất <h2> đến DOM. Kết quả:
Bây giờ, kiểm tra với mảng cars rỗng
const cars = [];
ReactDOM.render(
<Garage cars={cars} />, document.getElementById("root"));
Kết quả
Trong React để kết xuất một danh sách chúng ta ưu tiên dùng phương thức map(). Phương thức map() cho phép chúng ta chạy một hàm trên mỗi giá trị trong hàm và trả về một mảng mới. Xem xét ví dụ sau:
function Car(props) {
return <li>{props.brand}</li>;
}
function Garage() {
const cars = ['Ford', 'BMW', 'Audi'];
return (
<>
<h1>Bộ sưu tập xe của tôi gồm:</h1>
<ul>
{cars.map((car) => <Car brand={car}
/>)}
</ul>
</>
);
}
ReactDOM.render(<Garage />, document.getElementById("root"));
Kết quả:
Các mục (brand) trong danh sách trên không thể được kiểm soát bởi React khi chúng thay đổi, thêm hay xóa. Để giám sát các mục trong danh sách, React dùng key.
Các key giúp React xác định những mục nào đã thay đổi, được thêm vào hoặc bị loại bỏ. Các key phải được cấp cho các phần tử bên trong mảng để cung cấp cho các phần tử một danh tính ổn định. Thông thường chúng ta dùng id là key.
Ví dụ mảng cars từ ví dụ trên:
const cars = [
{ id: 1, brand: 'Ford' },
{ id: 2, brand: 'BMW' },
{ id: 3, brand: 'Audi' }
];
Các key chỉ có ý nghĩa trong ngữ cảnh xung quanh mảng. Xét ví dụ về các component Car và Garage, key được sử dụng trong Garage vì có khai báo mảng thay vì dùng key trong Car. Car và Garage sẽ được viết lại như sau:
function Car(props) {
return <li>{props.brand}</li>;
}
function Garage() {
const cars = [
{ id: 1, brand: 'Ford' },
{ id: 2, brand: 'BMW' },
{ id: 3, brand: 'Audi' }
];
return (
<>
<h1>Bộ sưu tập xe của tôi gồm:</h1>
<ul>
{cars.map((car) => <Car key={car.id} brand={car.brand}
/>)}
</ul>
</>
);
}
Các key được sử dụng trong các mảng phải là duy nhất giữa các anh chị em của chúng. Tuy nhiên, chúng không cần phải là duy nhất với phạm vi toàn cục. Chúng ta có thể sử dụng các key giống nhau khi tạo ra hai mảng khác nhau. Ví dụ sau hai mảng anh em sidebar và content dùng chung một mảng posts toàn cục nên có key giống nhau:
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
const posts = [
{ id: 1, title: 'Hello World', content: 'Welcome to learning React!' },
{ id: 2, title: 'Installation', content: 'You can install React from npm.' }
];
ReactDOM.render(
<Blog posts={posts} />, document.getElementById("root"));
Kết quả
Giống như HTML, React sử dụng các biểu mãu cho phép người dùng tương tác với trang web. Ví dụ sau đây tạo một form đơn giản cho phép người dùng nhập tên
import React from 'react';
import ReactDOM from 'react-dom';
function MyForm() {
return (
<form>
<label>Enter your name:
<input type="text" />
</label>
</form>
)
}
ReactDOM.render(
<MyForm />, document.getElementById('root'));
Chú ý, ví dụ trên sử dụng React Function thay vì React Class như các bài trên. Thực thi
Nếu nhập thông tin bất kỳ và nhấn Enter, kết quả trang sẽ được tải lại và xuất hiện dấu ? trên URL
Bình thường, biểu mẫu sẽ được gửi và trang sẽ làm mới. Nhưng điều này nói chung không phải là những gì chúng ta muốn xảy ra trong React. Chúng ta muốn ngăn chặn hành vi mặc định này và để React kiểm soát biểu mẫu.
Xử lý biểu mẫu là về cách bạn xử lý dữ liệu khi nó thay đổi giá trị hoặc được gửi. Trong HTML, dữ liệu biểu mẫu thường được xử lý bởi DOM và trong React, dữ liệu biểu mẫu thường được xử lý bởi các component. Khi dữ liệu được xử lý bởi các component, tất cả dữ liệu được lưu trữ ở trạng thái component. Chúng ta kiểm soát các thay đổi bằng cách thêm trình xử lý sự kiện trong thuộc tính onChange, và chúng ta có thể sử dụng useState Hook để theo dõi từng giá trị đầu vào. useState chỉ được sử dụng trong React Function (nên các ví dụ trong bài này chỉ dùng React Function) và sẽ được đề cập chi tiết hơn trong phần React Hook. Ví dụ về sử dụng onChange và useState trong biểu mẫu:
import { useState } from "react";
import ReactDOM from 'react-dom';
function MyForm() {
const [name, setName] = useState("");
return (
<form>
<label>Enter your name:
<input type="text" value={name} onChange={(e)=>
setName(e.target.value)}/>
</label>
</form>
)
}
ReactDOM.render(
<MyForm />, document.getElementById('root'));
Chúng ta cũng có thể kiểm soát các hoạt động gửi dữ liệu từ biểu mẫu (submit action) bằng cách thêm trình xử lý sự kiện trong thuộc tính onSubmit cho <form>. Ví dụ minh họa:
import { useState } from "react";
import ReactDOM from 'react-dom';
function MyForm() {
const [name, setName] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
alert(`The name you entered was: ${name}`);
}
return (
<form onSubmit={handleSubmit}>
<label>Enter your name:
<input type="text" value={name}
onChange={(e)=>setName(e.target.value)}/>
</label>
<input type="submit" />
</form>
)
}
ReactDOM.render(
<MyForm />, document.getElementById('root'));
Kết quả thực thi:
Chúng ta có thể kiểm soát các giá trị của nhiều trường đầu vào bằng cách thêm thuộc tính name cho mỗi phần tử. Kế tiếp, sẽ khởi tạo trạng thái với một đối tượng rỗng. Để truy cập các trường trong trình xử lý sự kiện, chúng ta sử dụng cú pháp event.target.name và event.target.value. Để cập nhật trạng thái, hãy sử dụng dấu ngoặc vuông [ký hiệu dấu ngoặc vuông] xung quanh tên thuộc tính.
Ví dụ sau đây tạo một biểu mẫu 2 trường username và age:
import { useState } from "react";
import ReactDOM from "react-dom";
function MyForm() {
const [inputs, setInputs] = useState({});
const handleChange = (event) => {
const name = event.target.name;
const value = event.target.value;
setInputs(values => ({ ...values, [name]: value }))
}
const handleSubmit = (event) => {
event.preventDefault();
alert(inputs);
}
return (
<form onSubmit={handleSubmit}>
<label>Enter your name:
<input type="text" name="username" value={inputs.username ||
"" } onChange={handleChange} />
</label>
<label>Enter your age:
<input type="number" name="age" value={inputs.age || "" }
onChange={handleChange} />
</label>
<input type="submit" />
</form>
)
}
ReactDOM.render(
<MyForm />, document.getElementById('root'));
Textarea trong HTML sử dụng phần tử <textarea>, ví dụ:
<textarea>
Content of the textarea.
</textarea>
Textarea trong React có một ít khác biệt. Trong React, textarea được đặt trong một thuộc tính giá trị. Chúng ta sẽ dùng useState Hook để quản lý giá trị của textarea như ví dụ sau:
import { useState } from "react";
import ReactDOM from "react-dom";
function MyForm() {
const [textarea, setTextarea] = useState("The content of a textarea goes in the value
attribute");
const handleChange = (event) => {
setTextarea(event.target.value)
}
return (
<form>
<textarea value={textarea} onChange={handleChange} />
</form>
)
}
ReactDOM.render(
<MyForm />, document.getElementById('root'));
Tương tự textarea, select cũng được sử dụng với một ít khác biệt so với HTML như ví dụ sau:
import { useState } from "react";
import ReactDOM from "react-dom";
function MyForm() {
const [myCar, setMyCar] = useState("Volvo");
const handleChange = (event) => {
setMyCar(event.target.value)
}
return (
<form>
<select value={myCar} onChange={handleChange}>
<option value="Ford">Ford</option>
<option value="Volvo">Volvo</option>
<option value="Fiat">Fiat</option>
</select>
</form>
)
}
ReactDOM.render(
<MyForm />, document.getElementById('root'));
Thực thi
Khi bắt đầu, hầu hết các trang web bao gồm một loạt các trang mà người dùng có thể điều hướng (navigating) bằng cách yêu cầu và mở các tập tin riêng biệt. Vị trí của tập tin hoặc tài nguyên hiện tại đã được liệt kê trong thanh vị trí (loaction bar) của trình duyệt. Các nút chuyển tiếp (forward) và quay lại (back) của trình duyệt sẽ hoạt động như mong đợi. Đánh dấu (bookmark) nội dung một website sẽ cho phép người dùng lưu tham chiếu đến một tập tin cụ thể có thể được tải lại theo yêu cầu của người dùng. Website dựa trên trang hoặc do máy chủ hiển thị, tính năng điều hướng và các tính năng lịch sử (history) của trình duyệt sẽ hoạt động như mong đợi.
Tuy nhiên, trong ứng dụng một trang (single-page app), tất cả các tính năng này đều trở nên có vấn đề. Hãy nhớ rằng, trong một ứng dụng một trang, mọi thứ đều diễn ra trên cùng một trang. JavaScript tải thông tin và thay đổi giao diện người dùng. Các tính năng như lịch sử trình duyệt, đánh dấu trang, nút chuyển tiếp và nút quay lại sẽ không hoạt động nếu không có giải pháp định tuyến (routing solution). Định tuyến (Routing) là quá trình xác định các điểm cuối (endpoints) cho các yêu cầu của khách hàng của bạn. Các điểm cuối này hoạt động cùng với các đối tượng lịch sử và vị trí của trình duyệt. Chúng được sử dụng để xác định nội dung được yêu cầu để JavaScript có thể tải và hiển thị giao diện người dùng thích hợp. Không giống như Angular, Ember hoặc Backbone, React không đi kèm với một bộ định tuyến tiêu chuẩn. Nhận thức được tầm quan trọng của giải pháp định tuyến, các kỹ sư Michael Jackson và Ryan Florence đã tạo ra một cái tên đơn giản là React Router. React Router đã được thông qua bởi cộng đồng như một giải pháp định tuyến phổ biến cho các ứng dụng React. Nó được sử dụng bởi các công ty bao gồm Uber, Zendesk, PayPal, Vimeo,...
Để cài đặt React Router phiên bản mới nhất, chúng ta gõ lệnh sau từ thư mục gốc của ứng dụng
npm i -D react-router-dom@latest
Để tạo một ứng dụng định tuyến các trang chúng ta bắt đầu bằng cấu trúc các tập tin. Trong thư mục src tạo thư mục con tên pages chứa các tập tin như sau:
Mỗi tập tin sẽ chứa một component đơn giản. Nội dung các tập tin như sau:
Layout.js
import { Outlet, Link } from "react-router-dom";
const Layout = () => {
return (
<>
<nav>
<uv>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link
to="/blogs">Blogs</Link>
</li>
<li>
<Link
to="/contact">Contact</Link>
</li>
</uv>
</nav>
<Outlet />
</>
)
};
export default Layout;
Thành phần Layout có các phần tử <Outlet> và <Link>
Bất cứ khi nào chúng ta liên kết đến một đường dẫn nội bộ, chúng ta sẽ sử dụng <Link> thay vì <a href="">.
Layout là một component được chia sẻ để chèn nội dung chung trên tất cả các trang, chẳng hạn như menu điều hướng.
Home.js
const Home = () => {
return <h1>Home</h1>;
};
export default Home;
Blogs.js
const Blogs = () => {
return <h1>Blog Articles</h1>;
};
export default Blogs;
Contact.js
const Contact = () => {
return <h1>Contact Me</h1>;
};
export default Contact;
NoPage.js
const NoPage = () => {
return <h1>404</h1>;
};
export default NoPage;
Và tập tin index.js của chúng ta sẽ có nội dung sau
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Layout from "./pages/Layout";
import Home from "./pages/Home";
import Blogs from "./pages/Blogs";
import Contact from "./pages/Contact";
import NoPage from "./pages/NoPage";
export default function RouterApp() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout
/>}>
<Route index element={<Home />}
/>
<Route path="blogs"
element={<Blogs />} />
<Route path="contact"
element={<Contact />} />
<Route path="*" element={<NoPage
/>} />
</Route>
</Routes>
</BrowserRouter>
);
}
ReactDOM.render(
<RouterApp />, document.getElementById("root"));
Trước tiên, chúng ta đặt toàn bộ nội dung vào trong phần tử <BrowserRouter>. Sau đó, chúng ta định nghĩa <Routes> là các tuyến chúng ta dùng để điều hướng. Một ứng dụng có thể có nhiều <Routes>. Ví dụ này chúng ta chỉ sử dụng một. Trong mỗi phần tử <Routes> sẽ chứa nhiều phần tử <Route>.
Các <Route> có thể được lồng vào nhau. <Route> đầu tiên có một đường dẫn / và hiển thị thành phần Layout. Đây là phần tử <Route> cha hay tuyến cha.
Các <Route> lồng nhau kế thừa và thêm vào <Route> cha. Vì vậy, đường dẫn blogs (thuộc tính path) được kết hợp với cha và trở thành / blogs.
Tuyến thành phần Home không có đường dẫn nhưng có thuộc tính index. Điều này có ý nghĩa rằng đây là tuyến mặc định cho tuyến cha, tức là chỉ cần dấu /.
Thiết lập thuộc tính path đến * sẽ hoạt động như một phương thức truy cập cho mọi URL không xác định. Điều này là tuyệt vời cho một trang lỗi 404.
Thuộc tính element của <Route> chỉ component cần định tuyến đến.
Thực thi ứng dụng
Nhấn liên kết Blogs
Nhấn liên kết Contact
Sử dụng CSS trong React có nhiều cách nhưng có 3 cách phổ biến sau:
Để chèn trực tiếp định nghĩa CSS dùng thuộc tính style trong React chúng ta phải dùng đối tượng JavaScript. Xét ví dụ sau (Mã hoàn chỉnh tại đây):
<h1 style={{color: "red" }}>Hello Style!</h1>
Chú ý thuộc tính style của h1:
Các thuộc tính trong CSS với hai từ trở lên cách nhau bởi dấu gạch ngang, ví dụ background-color, khi viết lại dưới dạng đối tượng JS phải tuân theo quy tắc (camel case syntax):
Ví dụ background-color viết lại thành backgroundColor (Mã hoàn chỉnh tại đây):
<h1 style={{backgroundColor: "lightblue" }}>Hello Style!</h1>
Nếu đối tượng có nhiều thông tin, chúng ta có thể tạo riêng đối tượng này như myStyle sau (Mã hoàn chỉnh tại đây):
const myStyle = {
color: "white",
backgroundColor: "DodgerBlue",
padding: "10px",
fontFamily: "Sans-Serif"
};
...
<h1 style={myStyle}>Hello Style!</h1>
Khai báo CSS và lưu trong một tập tin có phần mở rộng là .css, ví dụ App.css, sau đó liên kết đến ứng dụng React dùng lệnh import. Ví dụ liên kết tập tin App.css trong tập tin index.js và hai tập tin này được lưu trong cùng thư mục:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './App.css';
Nếu ứng dụng React sử dụng các component được định nghĩa trong nhiều tập tin khác nhau, một giải pháp dùng CSS là dùng các mô-đun CSS. Chi tiết về mô-đun CSS tham khảo tại đây.