https://github.com/sonhm3029/react_learning
https://github.com/sonhm3029/react_learning
Last synced: 3 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/sonhm3029/react_learning
- Owner: sonhm3029
- Created: 2021-12-13T00:59:35.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2022-03-01T14:21:12.000Z (over 3 years ago)
- Last Synced: 2025-01-17T01:24:32.350Z (5 months ago)
- Language: JavaScript
- Size: 1.92 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# REACTJS SELF LEARNING
[I. React.createElement()](#i-reactcreateelement)
[II. React-DOM](#ii-react-dom)
[III. JSX](#iii-jsx)
[IV. Props & Components](#iv-props--components)
[V. DOM Events](#v-dom-events)
[VI. Note JSX](#vi-note-jsx)
[VII. useState](#vii-usestate)
[VIII. useEffect](#viii-useeffect)
[IX. useLayoutEffect](#ix-uselayouteffect)
[X. useRef](#x-useref)
[XI. React.memo (HOC)](#xi-reactmemo-hoc)
[XII. useCallback](#xii-usecallback)
[XIII. useMemo](#xiii-usememo)
[XIV. useReducer](#xiv-usereducer)
[XV. useContext](#xv-usecontext)
[XVI. Sử dụng CSS và SCSS trong project với webpack](#xvi-su-dung-css-va-scss-trong-project-voi-webpack)
[XVII. React router V6](#xvii-react-router-v6)
## I. React.createElement()
Dùng để tạo ra các element trước khi render vào DOM, tương tự như `document.createElement()` nhưng có cú pháp và cách dùng như sau.
**Syntax:**
```Javascript
const reactElement = React.createElement(
type,
props,
children1,
children2,
...
);
```**Ví dụ:**
```html
- 1
- 2
- 3
-
```
Tạo với `React.createElement()`
```Javascript
const list = React.createElement(
"ul",
{
id: "demo",
style: {
backgroundColor: "blue",
position: "absolute"
}
},
React.createElement(
"li",
{
class: "item",
style: {
color: "red"
}
},
"1"
),
React.createElement(
"li",
{
class: "item",
style: {
color:"blue",
font-size:"1.6rem"
}
},
"2"
),
React.createElement(
"li",
null,
"3"
)
)
```
Đối với `type` của element thì có thể nhận các dữ liệu là `string`, `function`, `class`.
Đối với kiểu `function` hoặc `class` thường được dùng để tạo ra các layout cho các element => có thể dùng lại layout với các element mà không cần viết đi viết lại các layout đó.
**Ví dụ:**
```html
function Header() {
return (
<div className="header">New Header</div>
)
}
class Content extends React.Component {
render() {
return(<div className="content">Content</div>)
}
}
const app = (
<div className="wrapper">
<Header />
<Content />
<div className="footer">Footer</div>
</div>
)
ReactDOM.render(app, document.getElementById("root3"));
```
Kết quả:

Ta chú ý khi dùng kiểu `function` thì tên `function` phải được viết hoa chữ cái đầu tiên, nếu không sẽ bị báo lỗi và không thực hiện được.
Đối với `Class` thì phải kế thừa từ `React.Compenent` và hàm đặt tên là `render`
## II. React-DOM
### 1. ReactDOM.render()
**Syntax:**
```Javascript
ReactDOM.render(element, container, callback)
```
Ví dụ cho:
```html
```
Cần render:
```html
Heading
Đây là đoạn văn demo
```
vào trong thẻ div, `id="root"`
```Javascript
//Tạo element
const element = React.createElement(
"div",
{
id:"demo"
},
React.createElement(,
"h1",
{},
"Heading"
),
React.createElement(,
"p",
{},
"Đây là đoạn văn demo"
),
);
//Lấy ra container
const container = document.getElementById("root");
//Render
ReactDOM.render(element, container);
```
## III. JSX
- Để render được element vào `root` thì cần truyền vào tham số `element` là ReactElement mà ReactElement thì được tạo bằng các cú pháp `React.createElement()` => rất phức tạp.
Vì vậy mà ta dùng `JSX (Javascript XML)` có cấu trúc tương tự như HTML để tạo các `ReactElement`. Việc biên dịch từ JSX ra Javascript để tạo `ReactElement` được thực thi bởi thư viện `babel` của javascript. Thư viện này sẽ biên dịch đoạn code viết bằng JSX ra Javascript thực hiện tạo element như các ví dụ ở bài trước.
Xem live demo babel chuyển đổi JSX thành Javascript ở web sau:
[babel live demo](https://babeljs.io/repl#?browsers=&build=&builtIns=false&corejs=3.6&spec=false&loose=false&code_lz=MYewdgzgLgBApgGzgWzmWBeGAeAFgRgD4AJRBEGAdRACcEATbAegMKA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2015%2Creact&prettier=false&targets=&version=7.15.6&externalPlugins=&assumptions=%7B%7D)
**Ví dụ:**

Ví dụ render mảng dữ liệu với JSX:

Chú ý khi với `javascript` thường, mảng names sau khi map sẽ cần phải join lại để dùng vs `innerHTML` method còn đối với `JSX` thì việc truyền vào mảng là hợp lý đối với cú pháp `React.createElement`. Xem hình ảnh sau để hiểu rõ tại sao ta không cần map mảng names khi dùng với `React` và `JSX`

Ta thấy children của `ul` có kiểu là `Array` => hợp lý.
**Lưu ý:**
Khi ta muốn render cặp element sau:
```html
Heading
Parapraph
```
Ta không thể dùng JSX như sau:
```Javascript
const element = (
Heading
Paragraph
)
```
Vì đoạn code như trên sẽ có lỗi:

Vì nó sẽ được phiên dịch thành:
```Javascript
const element = (
React.createElement("h1",null,"Heading")
React.createElement("p",null,"Paragraph")
)
```
=> Không có syntax như vậy.
Nhìn vào error message ta thấy rằng, JSX element phải được wrap trong một thẻ nào đó. Như vậy đoạn code trên ta có thể sửa như sau:
```Javascript
const element = (
Heading
Paragraph
)
```
Tuy nhiên việc làm như trên sẽ làm sinh ra một thẻ `div` không mong muốn. Thay vì vậy ta có thể làm như sau:

Thẻ `React.Fragment` được coi như là một container ảo cho `JSX` element mà không sinh thêm thẻ nào trong html thật.
## IV. Props & Components
- Đối với React Element, props sử dụng khi tạo hoặc khi dùng với JSX thì giống với props của HTML bình thường khi props có một từ, từ 2 từ trở lên thì dùng camel case và có các trường hợp đặc biệt là `class` => `className` và `for` => `htmlFor`.
- Đối với React Components ( tạo ra bằng `function` hoặc `class`), ở đây ta sẽ xét được tạo bởi `function`, thì các props được sử dụng như các parameters truyền vào hàm và đặt theo nguyên tắc camelCase.
**Ví dụ:**
```Javascript
function Item() {
return (
This is heading
This is paragraph
)
}
function TestComponent(props) {
return (
{props.heading}
{props.paragraph}
)
}
// Dùng destructoring
function TestDestrucCom({
heading,
paragraph
}) {
return (
{heading}
{paragraph}
)
}
function App() {
return (
)
}
ReactDOM.render(, document.getElementById('root1'));
```
Xem kết quả và code tại: [react_components.html](react_components.html)
**Chú ý:** Khi render element qua mảng, sẽ xuất hiện warning như sau:

Để khắc phục lỗi này, ta có thể truyền một `prop` vào React element là `key`, các `key` của các element trong mảng phải khác nhau nên ta có thể để `key` là `index`.
**Ví dụ:**

**Ví dụ về components (tiếp):**
```html
const Form = {
Input() {
return <input style={{marginBottom: "12px"}}/>
},
Textarea() {
return <textarea style={{display:"block"}}></textarea>
}
}
ReactDOM.render(
<React.Fragment>
<Form.Input/>
<Form.Textarea/>
</React.Fragment>,
document.getElementById("root3")
)
```
## V. DOM Events
Xem ví dụ:
```html
const myBtn = (
<button onClick={function(){alert("ok")}}>Click me</button>
)
ReactDOM.render(myBtn, document.getElementById("root"));
```
Chú ý event được viết dưới dạng `camelCase`
## VI. Note JSX
- code được viết trong dấu `{}` từ các ví dụ trước trong JSX nhận vào là expresssion. Ví dụ nếu trong đó có xuất hiện `if`, `else` => lỗi.
- Cụ thể:
```Javascript
```
- Khi truyền vào prop vào làm parmeter thì default nó sẽ mang giá trị là true.
Ta có ví dụ sau:
```html
function Compo({class,children}) {
return <div className={class}>{children}</div>
}
const test = (
<div id="wrapper">
<Compo class="OK"><h1>Hello</h1></Compo>
</div>
)
```
Ta thấy với trường hợp dùng `Data`, thì Data ở đấy chính là children theo đúng cú pháp `React.createElement`.
## VII. useState
Dùng để thay đổi dữ liệu trong Component. Sử dụng trong function component.
**Ví dụ:**
```Javascript
import {useState} from 'react'
function App() {
const [counter, setCounter] = useState(1);
const handleIncreasing = () => {
setCounter(counter + 1);
}
return (
{counter}
Increase
);
}
export default App;
```
**Kết quả:**

Kết quả nhận được là mỗi lần ấn vào button sẽ tăng số đếm lên 1.
Như vậy ta có syntax:
```Javascript
[state, setState] = useState(initialState);
```
Trong đó:
- `initialState` là giá trị ban đầu của dữ liệu. Nếu truyền vào parameter là 1 hàm thì `initialState` là giá trị trả về của hàm chứ không phải hàm.
- `setState` sẽ là hàm để xử lý thay đổi dữ liệu của `state` với giá trị ban đầu của `state` là `initialState`. Như vậy `setState` là thay thế `state` bằng giá trị mới.
Nếu như ta truyền vào `setState` là một callback. Cùng với ví dụ bên trên, ta xét :
```Javascript
const handleIncreasing = () => {
setCounter(counter + 1);
setCounter(counter + 1);
setCounter(counter + 1);
}
```
Nếu như sử dụng hàm `handleIncreasing` như trên thì kết quả khi ấn nút `Increase` không thay đổi bởi vì khi gọi `setCounter(counter +1)` thì React sẽ gọi lại hàm component là `App` và khi đó `counter = counter +1`. Tức là với giá trị ban đầu của `counter = 1` ta sẽ nhận được:
```Javascript
const handleIncreasing = () => {
setCounter(2);
setCounter(2);
setCounter(2);
}
```
=> 3 hàm là như nhau, React sẽ chỉ gọi 1 lần. Trong trường hợp này, nếu muốn code hoạt động đúng là khi click button ta được số cộng thêm 3. ta sử dụng:
```Javascript
const handleIncreasing = () => {
setCounter( preCounter => preCounter + 1);
setCounter( preCounter => preCounter + 1);
setCounter( preCounter => preCounter + 1);
}
```
Trong đó `preCounter` là giá trị trước đó của state.
**Một số ví dụ của sử dụng useState, thay thế code ở function App() trong file [App.js](./my-react-app/src/App.js) bằng các đoạn code trong file sau:**
- [useState_example_1](./example/useState_example_1.js)
- [useState_example_2](./example/useState_example_2.js)
- [useState_example_3](./example/useState_example_3.js)
- [useState_example_4](./example/useState_example_4.js)
## VIII. useEffect()
Dùng trong trường hợp thay đổi các side effect.
Ví dụ:
- Update DOM
- Call API
- Listen DOM Events:
- Scroll
- resize window
- Timer function
**Syntax:**
```Javascript
import {useEffect} from 'react'
useEffect(callback);
useEffect(callback, []);
useEffect(callback, [deps]);
```
- Ứng với mỗi cách truyền parameter vào `useEffect()` thì `callback` đều được gọi mỗi khi `Component` được mounted vào DOM.
- `callback` trong `useEffect` chỉ được thực hiện khi component đã được thêm vào DOM.
**Đối với các cách truyền tham số:**
Cách dùng | chức năng
------------|------------
useEffect(callback)| `callback` được gọi mỗi khi component được re-render
useEffect(callback, [])| `callback` được gọi đúng một lần đầu tiên khi component được mounted
useEffect(callback, [deps])| `callback` sẽ được gọi mỗi khi `deps` thay đổi.
Xem các ví dụ:
- [useEffect_example_1](./example/useEffect_example_1.js)
- [useEffect_example_2](./example/useEffect_example_2.js)
**Lưu ý với cleaning up function dùng trong useEffect:**
- `Clean up function` luôn được gọi trước khi component được unmounted.
- `clean up function` được gọi trước khi callback được gọi( trừ lần mounted) -> trường hợp này thường được sử dụng đối với `useEffect(callback, [deps])`.
## IX. useLayoutEffect()
`useLayoutEffect()` cũng dùng để thực hiện các side effect như `useEffect()`. Thông thường `useEffect()` được sử dụng nhiều hơn vì `useLayoutEffect()` khá giống với `useEffect()`.
So sánh:
Các bước thực hiện trong DOM
`useLayoutEffect()` | `useEffect()`
--------------------|--------------
1.Cập nhật lại state|1.Cập nhật lại state
2.Cập nhật DOM (mutated)|2.Cập nhật DOM (mutated)
3.Gọi cleanup function nếu deps thay đổi(sync)|3.Render lại UI
4.Gọi callback(sync)|4.Gọi cleanup function nếu deps thay đổi(sync)|3.Render lại UI
5.Render lại UI|5.Gọi callback(sync)
## X. useRef
- `useRef` hook cho phép ta sử dụng các biến trong Component như 1 biến có phạm vi toàn cục, nghĩa là biến sẽ giữ nguyên khi Component re-render.
Ví dụ:
```Javascript
import {useState} from 'react'
function App() {
const [count, setCount] = useState(0);
let my_count = 0;
const handleCount = () => {
my_count +=1;
setCount(pre => pre +1);
}
console.log("my_count: ",my_count);
return (
<>
{count}
Click me
>
)
}
```
Ở ví dụ trên, khi Click vào button, hàm `handleCount` được gọi và tăng giá trị của biến `my_count` lên 1 đồng thời gọi `useState` nên Component sẽ được render lại. Ta có kết quả:

Ta thấy biến `my_count` sẽ được set lại liên tục mỗi khi Component được gọi vì nó có phạm vi cục bộ, chỉ tồn tại trong Component( vì Component chính là function).
Ta có thể giải quyết bằng cách dùng `useRef()` hook thay vì đưa `my_count` ra làm biến toàn cục.
```Javascript
import {useState, useRef} from 'react'
function App() {
const [count, setCount] = useState(0);
const my_count = useRef(0);
const handleCount = () => {
my_count.current +=1;
setCount(pre => pre +1);
}
console.log("my_count: ",my_count.current);
return (
<>
{count}
Click me
>
)
}
```
Kết quả:

Như vậy `useRef` có syntax:
```Javascript
const ref = useRef(initialValue);
```
Trong đó initialValue là giá trị khởi tạo khi Component được mounted. `useRef(initialValue)` sẽ trả về object.
Đoạn code trên sẽ có kết quả như sau:
```Javascript
ref = {
current: initialValue
}
```
Như vậy ta có thể truy cập vào giá trị mà ta muốn dùng bằng cách sử dụng `ref.current`.
- Một cách dùng khác của `useRef` đó là truy cập và lưu trữ trực tiếp element trong DOM.
- Trong React, ta có thể thêm `ref` attribute cho element, sử dụng nó với `useRef` để truy cập nó trực tiếp trong DOM.
**Ví dụ:**
```Javascript
import {useRef} from 'react'
function App() {
const inputElement = useRef();
const focusInput = () => {
inputElement.current.focus();
};
return (
<>
Focus Input
>
);
}
```
Ví dụ trên, biến `inputElement` dùng để truy cập trực tiếp đến `input` element.
- Cuối cùng ta sẽ nói đến cách dùng `useRef` để keep track với giá trị trước khi `setState` của `useState`.
**Ví dụ:**
```Javascript
import { useState, useEffect, useRef } from "react";
function App() {
const [inputValue, setInputValue] = useState("");
const previousInputValue = useRef("");
useEffect(() => {
previousInputValue.current = inputValue;
}, [inputValue]);
return (
<>
setInputValue(e.target.value)}
/>
Current Value: {inputValue}
Previous Value: {previousInputValue.current}
>
);
}
```
Lý giải: Khi Component được mounted ra thì callback của `useEffect` được gọi nên:
```Javascript
previousInputValue.current = inputValue;
```
Sau đó khi ta gõ vào ô input, ví dụ gõ kí tự `a` thì `onChange` event sẽ gọi đến `setInputValue('a')`. Như vậy Component được `setState` nên sẽ được render lại, khi đó `inputValue = 'a'` do nhận được từ `setInputValue('a')`.
Khi `inputValue` thay đổi đồng nghĩa với với việc `useEffect` sẽ được gọi và có thứ tự thực hiện như sau:
1. `inputValue` thay đổi
2. Cập nhật lại DOM.
3. Render lại UI, lúc này do `callback` của `useEffect` chủa được gọi nên `previousInputValue.current` vẫn mang giá trị trước đó của `inputValue` là `''`. Vì vậy mà hiển thị ra màn hình sẽ là giá trị trước đó của `inputValue` và giá trị hiện tại của `inputValue`.
4. Gọi cleanup function nếu có
5. Gọi `callback` của `useEffect`, lúc này thì `previousInputValue.current` mới được cập nhật lên là giá trị hiện tại của `inputValue` và khi ta click button thì quá trình từ 1 đến 5 lại được lặp lại.
## XI. React.memo (HOC)
Xét ví dụ sau:
Trong file `Content.js`
```Javascript
function Content() {
console.log('re-render');
return (
This is children component
);
}
export default Content;
```
Trong file `App.js`
```Javascript
import {useState} from 'react'
import Content from './Content.js'
function App() {
const [count, setCount] = useState(0);
return (
<>
{count}
setCount(count + 1)}>Click me
>
)
}
```
Ta thấy khi click button thì state của `App` thay đổi dẫn đến việc `App` Component re-render đồng thời làm cho `Content` component bị re-render theo mặc dù state của `Content` component không đổi.
Để khắc phục việc này (giúp tăng performance) thì ta có thể dùng `memo` của react bằng cách.
Trong file `Content.js`
```Javascript
import {memo} from 'react'
function Content() {
// Code goes here
}
export default memo(Content)
```
Nếu không destructuring `memo` từ `react` ra thì có thể dùng `React.memo()`.
Như vậy, `Content` component sẽ không bị re-render theo Component cha là `App` component nếu như không có state nào của nó bị thay đổi.
Nếu như có state nào của `Content` bị thay đổi thì nó vẫn re-render bình thường.
## XII. useCallback
Xét ví dụ như khi dùng `memo` nhưng thay đổi như sau:
Trong file `Content.js`
```Javascript
import {memo} from 'react'
function Content({increasing}) {
console.log('re-render');
return (
<>
This is children component
Click me
>
);
}
export default Content;
```
Trong file `App.js`
```Javascript
import {useState} from 'react'
import Content from './Content.js'
function App() {
const [count, setCount] = useState(0);
const increasing = () => {
setCount(pre => pre +1);
}
return (
<>
{count}
>
)
}
```
Kết quả:

Như ta thấy mặc dù đã dùng memo và không có properties nào của `Content` component bị thay đổi thế nhưng nó lại vẫn bị re-render.
Thực tế thì vẫn có một property của `Content` bị thay đổi đó chính là hàm `increasing`. Lý do khi `App` component thay đổi state, nó sẽ re-render và hàm `increasing` của nó để truyền vào `Content` component cũng sẽ bị re-declared ( thay đổi tham chiếu) mặc dù nội dung của nó không đổi. Vì vậy nên `Content` component vẫn bị re-render.
Để khắc phục việc này, cải thiện performance thì ta có thể dùng `useCallback` hook
**Syntax:**
```Javascript
useCallback(callback, [deps]);
```
đối với ví dụ trên ta sử dụng như sau trong file `App.js`
```Javascript
import {useState, useCallback} from 'react'
import Content from './Content.js'
function App() {
const [count, setCount] = useState(0);
const increasing = useCallback(() => {
setCount( pre => pre +1);
},[]);
return (
<>
{count}
>
)
}
```
Bây giờ thì `Content` component sẽ chỉ thay đổi nếu như props nào đó của nó thay đổi.
**Chú ý:**
- Việc sử dụng `useCallback` thường được dùng với `memo` để khắc phục những việc mà `memo` chưa làm được như ví dụ trên là sự thay đổi tham chiếu hàm.
## XIII. useMemo
`useMemo` hook khá giống `useCallback`. Khác nhau ở điểm là `useCallback` thì trả về một `memoized function` còn `useMemo` thì trả về một `memoized value` nào đó.
Xét ví dụ:
```Javascript
import {useState} from 'react'
function App() {
const [productName, setProductName] = useState('');
const [productPrice, setProductPrice] = useState('');
const [listProducts, setListProducts] = useState([]);
const handleAddProduct = () => {
setListProducts( pre => [...pre, {
name: productName,
price: Number(productPrice)
}]);
setProductName('');
setProductPrice('');
}
const total = listProducts.reduce((sum, product) => {
console.log('Tính toán tại');
return sum + product.price;
},0)
return (
<>
setProductName(e.target.value)}
/>
setProductPrice(e.target.value)}
/>
Thêm sản phẩm
Total: {total}
- {product.name} - {product.price}
{listProducts.map((product, index) => {
return (
)
})}
>
)
}
```
Kết quả:

Ta thấy, sau khi thêm một sản phẩm vào list. Khi ta thực hiện nhập sản phẩm tiếp theo thì `'Tính toán lại'` liên tục được gọi ra. Lý do khi ta nhập input `onChange` event sẽ được gọi làm thay đổi state của component dẫn đến việc component bị re-render.
Mỗi lần re-render, nó sẽ gọi lại một lần `listProducts.reduce(...)`, Chính vì thế mà nó in ra `'Tính toán lại'` mặc dù sản phẩm mới chưa được thêm vào. Để rõ hơn ta có thể đặt một dòng log trong component để thấy.
Để giải quyết vấn đề tính toán lại không cần thiết này, ta dùng `useMemo` hook.
`useMemo` sẽ giúp cho giá trị không cần tính toán lại nếu như không có `[deps]` nào của nó bị thay đổi.
**Syntax:**
```Javascript
import {useMemo} from 'react'
useMemo(callback, [deps]);
```
Ta dùng với ví dụ trên như sau. Thay đoạn code sau
```Javascript
const total = listProducts.reduce((sum, product) => {
console.log('Tính toán tại');
return sum + product.price;
},0)
```
thành dùng với `useMemo`
```Javascript
import {useMemo, useState} from 'react'
function App() {
//...
const total = useMemo(()=> {
const result = listProducts.reduce((sum, product) => {
console.log('Tính toán lại');
return sum + product.price;
},0);
return result;
},[listProducts])
}
```
Kết quả:

Như vậy chỉ khi nào `[deps]` là `listProducts` thay đổi thì total mới được tính toán lại.
Ta thấy màn hình hiện ra 3 lần `'Tính toán lại` là do `console` được đặt trong reduce, nên mỗi lần duyệt qua một phần tử mảng nó sẽ in ra một lần.
## XIV. useReducer
Về tổng quan thì `useReducer` có cách dùng giống với `useState` là dùng cho Component có state thay đổi. Điểm khác biệt ở chỗ ở những ứng dụng có state thay đổi đơn giản thì ta dùng `useState` còn đối với một project lớn, xử lý state changed phức tạp thì ta nên sử dụng `useReducer`.
Xem qua ví dụ dưới đây để thấy cách làm việc với useReducer.
Làm ứng dụng tăng hoặc giảm số lượng:
Giao diện và chức năng mong muốn như sau:

So sánh nếu dùng `useState` để làm so với dùng `useReducer`:
`useState` | `useReducer`
-----------|-------------
1.Init state: 0 | 1.Init state: 0
2.Actions: Up(state +1), Down(state-1) |2.Actions: Up(state +1), Down(state)
---|3.call Reducer
---|4.Dispatch
Code ứng dụng với `useReducer`
```Javascript
import {useReducer} from 'react'
const DOWN = 'down';
const UP = 'up';
const reducer = (state, action) => {
switch(action) {
case UP:
return state + 1;
case DOWN:
return state -1;
default:
throw new Error("Không tồn tại chức năng")
}
}
function App() {
const [count, dispatch] = useReducer(reducer, 0);
return (
{count}
dispatch(UP)}>Up
dispatch(DOWN)}>Down
)
}
```
Từ ví dụ trên. Ta thấy `useReducer` có Syntax:
```Javascript
const [state, dispatch] = useReducer(reducerFunction, initialValue)
```
Ví dụ sử dụng `useReducer` làm totolist:
```Javascript
import {useReducer} from 'react'
const initialValue = {
work:'',
listWorks:[]
}
const SET_WORK = 'set';
const ADD_WORK = 'add';
const DELETE_WORK = 'delete';
const set_work = (payload)=> {
return {
payload,
type: SET_WORK
}
}
const add_work = (payload)=> {
return {
payload,
type: ADD_WORK
}
}
const delete_work = (payload)=> {
return {
payload,
type: DELETE_WORK
}
}
const reducer = (state, action) => {
switch(action.type) {
case SET_WORK:
return {
...state,
work:action.payload
};
case ADD_WORK:
return {
work:'',
listWorks: [...state.listWorks, action.payload]
}
case DELETE_WORK:
const newListWorks = [...state.listWorks];
newListWorks.splice(action.payload,1);
return {
...state,
listWorks: newListWorks
}
default:
throw new Error("Something wrong!");
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialValue);
const {work, listWorks} = state;
const inputElement = useRef();
const handleAddWork = () => {
dispatch(add_work(work));
inputElement.current.focus();
}
console.log(state);
return (
<>
dispatch(set_work(e.target.value))}
/>
Add
Danh sách công việc
-
{work}
dispatch(delete_work(index))}
style={{marginLeft:10}}
>
✖
{listWorks.map((work, index) => {
return (
)
})}
>
)
}
```
Cop code thay thế vào file `App.js` để xem kết quả.
Ta thấy rằng việc sử dụng `useReducer` trong bài toán đơn giản như vậy là không nên, vì so với sử dụng `useState` thì nó làm code dài hơn, phức tạp hơn rất nhiều.
Vì vậy phải cân nhắc khi nào cần dùng hook nào.
## XV. useContext
Vấn đề đặt ra:
Khi ta cần truyền state của component cha vào prop của component con để component con có thể sử dụng được, đối với việc dùng một nested component gồm nhiều component lồng nhau sẽ trở nên dài dòng, phức tạp.
**Ví dụ:**
```Javascript
import {useState} from 'react'
function Component1() {
const [state, setState] = useState('ok');
return (
);
}
function Component2({content}) {
return (
)
}
function Component3({content}) {
return (
);
}
function Component4({content}) {
return (
<>
This is the content:
{content}
>
)
}
```
Như vậy để dùng được `state` của `Component1` ở `Component4` thì ta phải truyền liên tục `state` đó vào prop của từng component con để có thể đến được `Componet4`.
Để khắc phục việc truyền phức tạp như thế thì ta có thể dùng `useContext` hook để đưa `state` của component cha trở lên accessiable cho tất cả các component con của nó có thể truy cập trực tiếp mà không cần truyền qua từng component con một.
Với ví dụ trên, ta làm như sau:
Đầu tiên để dùng `useContext` hook thì ta cần tạo context bằng `createContext`. Rồi sau đó ta sẽ dùng `Context Provider` bọc lấy hệ thống nested component để có thể sử dụng context truyền đi.
```Javascript
import {useState, useContext, createContext} from 'react'
// Tạo context
const exampleContext = createContext();
function Component1 () {
const [state, setState] = useState('ok');
return (
);
}
```
Bây giờ tất cả các component trong hệ thống nested component để có thể truy cập tới `context` đã đặt.
Để truy cập tới `context` đã đặt tại component bất kì trong hệt thống nested component, trong ví dụ này là `Component4` ta làm như sau:
```Javascript
function Component4() {
const context = useContext(exampleContext);
return (
<>
This is the content:
{context}
>
)
}
```
## XVI. Sử dụng CSS và SCSS trong project với webpack
Có các cách để CSS trong react app như sau:
- Inline styling
- CSS stylesheets
- CSS modules
1. Inline styling:
- Có thể sử dụng hai cách:
- Sử dụng trực tiếp trong element:
```Javascript
```
Đối với cách này thì chú ý các css-props cần phải viết dưới dạng camelCase nếu có từ 2 từ trở lên.
- Sử dụng styles như một object:
```Javascript
const style = {
backgroundColor: 'red',
color: 'white'
}
```
2. CSS stylesheets
Ta có thể dùng CSS stylesheets để CSS như bình thường và import vào project để dùng.
Ví dụ ta có file `App.css`
```CSS
.my_css {
background-color: red;
color: white;
}
```
Trong file `App.js`
```Javascript
import './App.css'
function App() {
return (
)
}
```
Với cách này thì với việc phân tách ra nhiều file css có thể dẫn đến việc trùng class, trùng CSS một cách không mong muốn nên cần phải lưu ý.
3. CSS modules
Đối với việc sử dụng CSS modules thì ta sẽ khắc phục được vấn đề tách ra nhiều file css riêng cho từng component mà không sợ bị trùng tên class.
Để sử dụng CSS modules ta sẽ đặt tên các file CSS theo cứ pháp: `.module.css`.
Ví dụ CSS cho component `Content`:
Trong `/Content/Content.module.css`
```CSS
.heading {
color: red;
}
#content {
background-color: white;
}
```
Trong `/Content/index.js`
```Javascript
import styles from './Content.module.css'
function Content() {
return (
Đây là tiêu đề
)
}
export default Content;
```
Sau đó sử dụng `Heading` component bình thường trong file App...
Đối với việc sử dụng CSS module thì `React` sẽ tự động sinh ra các `class` hay `id` theo cú pháp `__<đường dẫn đến file được mã hóa>`. Vì vậy mà ta không lo sợ việc bị trùng lặp `class` CSS không mong muốn.
Kết quả:

Ta thấy khi sử dụng CSS stylesheets hay modules trong react thì Webpack đều chuyển chúng về các thẻ `` và thêm vào đầu `` của trang. Còn khi ta chạy `npm run build` thì Webpack sẽ thêm tất cả các CSS từ các file vào trong một file duy nhất và được tối thiểu hóa lại.
Kết quả:

Chú ý rằng, phương pháp làm unique `class` này thường chỉ được sử dụng với từng `Component` và hoạt động với `class` hoặc `id`... Nếu ta đưa vào file các thẻ tag như `
`, `
`... Thì việc CSS cho các thẻ này sẽ hoạt động toàn cục, không tạo được unique như `class`.
Để sử dụng CSS cho các thẻ `tag` như trên thì ta có hai hướng:
- CSS cho các thẻ đó bằng CSS stylesheets, nhưng chú ý nên tạo ra một component ôm toàn bộ trang và đặt stylesheets cho nó => như vậy ta sẽ được CSS toàn cục thay vì việc dùng trực tiếp lên file `App.css`
- Sử dụng `sass` để css nested component...
### Sử dụng multiple class thế nào đối với CSS module ?
Việc sử dụng multiple class có thể được thực hiện bằng `JS`. ví dụ:
```CSS
/* Đây là file Content.module.css */
.dFlex {
display: flex;
}
.bgPrimary {
background-color: blue;
}
```
```Javascript
import styles from './Content.module.css'
function Content() {
return (
)
}
```
Hoặc
```Javascript
import styles from './Content.module.css'
function Content() {
return (
)
}
```
Ngoài ra ta còn có thể sử dụng thư viện trợ giúp:
- `clsx`
- `classnames`
Hai thư viện này tính năng tương tự nhau, có thể tự tìm hiểu trên `npm`.
Khuyến khích dùng `clsx`
Tải về:
```shell
>npm install clsx
```
Để dùng:
```Javascript
import styles from './Content.module.css'
import clsx from 'clsx'
function Content() {
return (
)
}
```
Ngoài ra để sử dụng `sass` thì chỉ cần install `sass` và sử dụng như với CSS stylesheets hoặc CSS modules.
## XVII. React router V6
Dùng để định tuyến các page của trang web, với mục đích không làm refresh lại trang (SPA).
Để sử dụng ta cần install:
Install phiên bản v6
```shell
>npm i react-router-dom@6
```
Hoặc Install phiên bản mới nhất:
```shell
>npm i react-router-dom@latest
```
Tiếp theo, người ta thường tạo một cấu trúc thư mục như sau:
`src\pages\`:
- `Layout.js`
- `Home.js`
- `Contact.js`
- `Blogs.js`
- `NoPage.js`
Đầu tiên cần bọc toàn bộ Component `App` trong `index.js` với `BrowserRouter` như sau:

Mỗi web thì chỉ có một bộ định tuyến như vậy.
Tiếp theo trong file `App.js` ta cần import `Route` và `Routes` từ thư viện `react-router-dom`. Trong đó, `Routes` là thẻ ôm toàn bộ đoạn code định tuyến, nội dụng của các component pages sẽ được render tại vị trí này. Trong thẻ `Routes`, đặt các thẻ `Route` link tới page components. Xem ví dụ:

Giải thích:
- Thẻ `` để định tuyến có thể nested. Như ví dụ trên, trong thẻ `` định tuyến tới Component `LayoutPage`, ta đặt `path` là `'/'`. Vì vậy các `` nằm trong sẽ được kế thừa `path` từ thẻ `` ôm nó. Ví dụ `` định tuyến tới `BlogPage` sẽ có `path` được kết hợp từ `'/'` và `path` của nó là `'blogs'` thành `/blogs`.
- `` định tuyến tới `HomePage` thì không có `path` nhưng nó có `index` tức là nó sẽ định tuyến default giống với `` của `LayoutPage`
- Đối với `NoPage` thì `` có `path='*'` tức là nó sẽ đi đến page này nếu như gặp `undefined` route/url. Đây là trang 404 not found.
Trong file `Layout.js`:

Giải thích:
Ta dùng `` tag của thư viện `react-router-dom` thay vì dùng `` Vì thẻ `` sẽ làm reload lại page, như vậy không đảm bảo SPA. Còn `` sẽ không reload lại page.
Thẻ `` của thư viện `react-router-dom` là vị trí render component khi các route được gọi.
Do Layout route bọc tất cả các route còn lại nên navbar sẽ được kế thừa qua các route trở thành template của page.
Trong các file pages còn lại :
Blogs.js
```Javascript
function Blogs() {
return (
Blogs page
)
}
export default Blogs;
```
Contact.js
```Javascript
function Contact() {
return (
Contact page
)
}
export default Contact;
```
Home.js
```Javascript
function Home() {
return (
Home page
)
}
export default Home;
```
NoPage.js
```Javascript
function NoPage() {
return (
NoPage page
)
}
export default NoPage;
```
Kết quả:



## XVIII. Redux
State management library, dùng để quản lý state cho project có state phức tạp, khó quản lý.
Các khái niệm cần biết:
- Store
- Actions
- Reducers
Install module:
```Shell
> npm install redux react-redux
```
hoặc
```Shell
> yarn add redux react-redux
```
Basic knowledge về redux:
Bao gồm:
- `createStore(reducer)`
- `reducer(state,action)`
- `store.getState()`
- `store.dispatch(action)`
[Ví dụ](https://codesandbox.io/s/redux-example-ro4yro?file=/src/App.js)
Kết nối redux với React:
[Ví dụ](https://codesandbox.io/s/priceless-bardeen-8p1lsp)
Để sử dụng redux với React ta cần làm như sau:
Tạo folder redux chứa các file khai báo reducer và store:

Tạo các file khai báo reducer, ví dụ với `todo.js`:
```Javascript
const initState = {
items:[]
};
const ADD_TODO = "ADD_TODO";
export const addTodo = (text) => ({
type: ADD_TODO,
payload: text
});
const reducer = (state=initState,action)=> {
switch(action.type) {
case "ADD_TODO":
return {
...state,
items: [...state.items,action.payload]
}
default:
return state;
}
}
export default reducer;
```
trong đó nên gán `initState` cho reducer để thực hiện `combineReducers`. Hàm `addTodo` là action creater.
Trong file `store.js`, tạo store
```Javascript
import { createStore, combineReducers } from "redux";
import todoReducer from "./todo";
const reducer = combineReducers({
todo: todoReducer
});
export default createStore(reducer);
```
**Kết nối redux với react với redux connect:**

Sử dụng HOC `connect` của `react-redux`.
Trong đó, `connect` nhận vào 2 tham số (tùy cách đặt tên):
- Tham số đầu tiên là hàm `mapStateToProps`. Hàm này nhận vào state tổng, hay là state của store, được gọi khi state của store thay đổi và trả về state mà components muốn connect cần. Trong trường hợp này là state của `TodoApp`.
- Tham số thứ hai là `mapActionsToProps` có thể là function hoặc object:
- Nếu là function thì nó nhận vào tham số `dispatch`, trả về Object trong đó có các function `dispatch` action. Lưu ý do là dispatch action nên tham số nhận vào của dispatch phải có giá trị cụ thể.
Ví dụ:
```Javascript
const mapActionsToProps = (dispatch) => ({
addTodo: (text) => dispatch({
type:"ADD_TODO",
payload:text
})
//Hoặc
//addTodo: (text) => dispatch(addTodo(text))
})
```
- Nếu là Object thì trong Object có các hàm thành viên là các action creater như ảnh đầu tiên. (**Recommend cách này vì nó là shorthand**), ngắn gọn oke hơn. Các action creater trong object này sẽ tự động dispatch action khi nó được gọi trong component.
Hai tham số `mapStateToProps` và `mapActionToProps` sẽ được truyền vào component như props, chú ý tên đặt trong khai báo ở file connect.
```Javascript
import React, { useState } from "react";
export default function TodoApp({ todos, addTodo }) {
const [text, setText] = useState("");
return (
setText(e.target.value)}
/>
addTodo(text)}>Add
- {todo}
{todos.map((todo) => (
))}
);
}
```
Cuối cùng để sử dụng được store thì ta cần bọc toàn bộ app bằng `Provider` của `redux` và truyền vào đó store:
Trong file `index.js`:
```Javascript
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import {Provider} from 'react-redux';
import store from './redux/store';
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
,
rootElement
);
```
Để sử dụng `Redux Devtools` thêm vào trong file `store.js` đoạn sau:
```Javascript
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
```

Đọc thêm ở đây:
[Redux Devtools guide](https://github.com/reduxjs/redux-devtools/tree/main/extension#installation)

Với cửa số làm việc như trên thì có:
- Action: các action hiện có của store
- State: state hiện tại
- Diff: state trước đó
Phía bên tay trái là các action được dispatch.
## XIX. Redux middleware

[Ví dụ](https://codesandbox.io/s/strange-dew-7ubo8r?file=/src/redux/store.js)
Trong file `store.js`
```Javascript
import {
createStore,
combineReducers,
applyMiddleware
} from "redux";
import todoReducer from "./todo";
const reducer = combineReducers({
todo: todoReducer
});
const myMiddleware = store => next => action => {
console.log(next);
console.log(action);
return next(action);
}
export default createStore(
reducer,
applyMiddleware(myMiddleware),
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
```
- import `applyMiddleware` và sử dụng như đoạn code trên. Trong đó `applyMiddware(mw1,mw2...)` nhận vào tham số là các middleware tự định nghĩa như `myMiddleware`.
- `myMiddleware` là currying function trong đó các tham số:
- store: store tạo bởi `createStore()` của redux
- next: dispatch,
- action: action

Ví dụ có thể sử dụng middleware để check nội dung nhập vào của action, hide đi action mang payload có nội dung bậy bạ:
```Javascript
const myMiddleware = store => next => action => {
if(action.payload==="fuck") {
action.payload = "***";
}
return next(action);
}
```
### Redux thunk
Ví dụ với sử dụng async middleware với redux:
Với việc set todolist ban đầu, thay vì việc gọi axios ngay trong `useEffect` của component thì ta làm như sau
```Javascript
import React, {
useState,
useEffect
} from "react";
export default function TodoApp({
todos, addTodo, fetchTodo
})
{
useEffect(()=> {
fetchTodo();
},[fetchTodo])
const [text, setText] = useState("");
return (
setText(e.target.value)}
/>
addTodo(text)}>Add
- {todo.title}
{todos.map((todo) => (
))}
);
}
```
Trong đó `fetchTodo` được định nghĩa trong file `todo.js`
```Javascript
import axios from "axios";
const initialState = {
items: []
};
const ADD_TODO = "ADD_TODO";
const SET_TODO = "SET_TODO";
export const addTodo = (text) => ({
type: ADD_TODO,
payload: text
});
export const setTodo = (todos)=> ({
type:SET_TODO,
payload:todos
})
export const fetchTodo = async(dispatch) => {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos")
dispatch(setTodo(res.data));
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TODO:
return {
...state,
items: [...state.items, {
title:action.payload
}]
};
case SET_TODO:
console.log(action.payload);
return {
...state,
items:action.payload
}
default:
return state;
}
};
export default reducer;
```
Với `fetchTodo` được truyền vào từ redux connect như sau:
```Javascript
import axios from "axios";
import { connect } from "react-redux";
import TodoApp from "../components/TodoApp";
import { addTodo,setTodo, fetchTodo} from "../redux/todo";
const mapStateToProps = (state) => ({
todos: state.todo.items
});
// const mapActionsToProps = {
// addTodo,
// setTodo
// };
const mapActionsToProps = (dispatch) => ({
addTodo: (text)=> dispatch(addTodo(text)),
setTodo: (todos)=> dispatch(setTodo(todos)),
fetchTodo: ()=> dispatch(fetchTodo)
})
export default connect(mapStateToProps, mapActionsToProps)(TodoApp);
```
Ta sẽ thấy có lỗi như sau:

Như vậy để có thể sử dụng được `fetchTodo` ta import `redux-thunk` và sử dụng như sau:
```Javascript
import { createStore, combineReducers, applyMiddleware } from "redux";
import todoReducer from "./todo";
import thunk from "redux-thunk";
const reducer = combineReducers({
todo: todoReducer
});
const myMiddleware = (store) => (next) => (action) => {
if (action.payload === "fuck") {
action.payload = "***";
}
return next(action);
};
// const asyncMiddleware = (store) => (next) => (action) => {
// if (typeof action === "function") {
// action(next);
// }
// };
export default createStore(
reducer,
applyMiddleware(myMiddleware,thunk)
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
```
Ta thấy ở middleware thứ nhất, do `redux` không chấp nhận `fetchTodo`, vì đây không phải là `action creater` function. Cho nên sẽ chuyển qua `middleware` tiếp theo là `asyncMiddleware`, tại đây thì ta đã xử lý được bằng cách tự `dispatch`.
Tuy nhiên thay vì phải define ra đoạn code `asyncMiddleware` như trên thì ta có thể sử dụng bằng `redux-thunk`.
## XX Redux-toolkit
Có thể sử dụng `configureStore` của `@reduxjs/toolkit`. Việc sử dụng `configureStore` thay cho `createStore` sẽ không cần phải thêm `thunk` và `redux devtool` cũng như một số middleware khác vào `createStore` như cũ vì nó đã được tích hợp sẵn.
Vì vậy thay vì:
```Javascript
import { createStore, combineReducers, applyMiddleware } from "redux";
import todoReducer from "./todo";
import thunk from "redux-thunk";
const reducer = combineReducers({
todo: todoReducer
});
const myMiddleware = (store) => (next) => (action) => {
if (action.payload === "fuck") {
action.payload = "***";
}
return next(action);
};
export default createStore(
reducer,
applyMiddleware(myMiddleware,thunk)
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
```
thì ta có:
```Javascript
import { createStore, combineReducers, applyMiddleware } from "redux";
import todoReducer from "./todo";
import {configureStore} from '@reduxjs/toolkit';
const reducer = combineReducers({
todo: todoReducer
});
const myMiddleware = (store) => (next) => (action) => {
if (action.payload === "fuck") {
action.payload = "***";
}
return next(action);
};
export default configureStore({
reducer
})
```