<
!
DOCTYPE
html
>
<
html
lang
=
“en”
>
<
head
>
<
meta
charset
=
“UTF-8”
/
>
<
meta
http
–
equiv
=
“X-UA-Compatible”
content
=
“IE=edge”
/
>
<
meta
name
=
“viewport”
content
=
“width=device-width, initial-scale=1.0”
/
>
<
title
>
ReactJS
<
/
title
>
<style>
table
{
border-collapse
:
collapse
;
}
th,
td
{
padding
:
0
0.5rem
;
border
:
1px
solid
black
;
}
</style>
<
/
head
>
<
body
>
<
div
id
=
“root”
>
<
/
div
>
<script
src
=
“https://cdnjs.cloudflare.com/ajax/libs/redux/4.1.0/redux.js”
>
</script>
<script
src
=
“https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.4/react-redux.js”
>
</script>
<
!
—
===
===
===
==
src
/
utils
.
js
===
===
===
==
—
>
<script>
class
Uuid
{
s4
(
)
{
return
Math
.
floor
(
(
1
+
Math
.
random
(
)
)
*
0x10000
)
.
toString
(
16
)
.
substring
(
1
)
;
}
s16
(
)
{
return
this
.
s4
(
)
+
this
.
s4
(
)
+
this
.
s4
(
)
+
this
.
s4
(
)
;
}
}
let
uuid
=
new
Uuid
(
)
;
</script>
<
!
—
===
===
===
==
slices
/
listStudent_slice
.
js
===
===
===
==
—
>
<script>
//Dùng RTK.createSlice khai báo tất cả State, Reducers và Actions luôn
let
listStudent_local
=
JSON
.
parse
(
localStorage
.
getItem
(
‘listStudent’
)
)
||
{
data
:
[
]
,
loading
:
false
}
;
const
listStudent_slice
=
RTK
.
createSlice
(
{
name
:
‘listStudent’
,
initialState
:
listStudent_local
,
reducers
:
{
action_saveStudent
(
state
,
action
)
{
if
(
!
action
.
payload
.
id
)
{
let
new_student
=
{
id
:
uuid
.
s16
(
)
,
username
:
action
.
payload
.
username
,
:
action
.
payload
.
,
}
;
state
.
data
.
push
(
new_student
)
;
}
else
{
let
find_student
=
state
.
data
.
find
(
(
{
id
}
)
=
>
id
==
action
.
payload
.
id
)
;
find_student
.
username
=
action
.
payload
.
username
;
find_student
.
=
action
.
payload
.
;
}
localStorage
.
setItem
(
‘listStudent’
,
JSON
.
stringify
(
state
)
)
;
return
state
;
}
,
action_deleteStudent
(
state
,
action
)
{
state
.
data
=
state
.
data
.
filter
(
(
{
id
}
)
=
>
id
!=
action
.
payload
)
;
localStorage
.
setItem
(
‘listStudent’
,
JSON
.
stringify
(
state
)
)
;
return
state
;
}
,
action_setListStudent
(
state
,
action
)
{
state
.
data
=
action
.
payload
.
map
(
item
=
>
(
{
username
:
item
.
username
,
:
item
.
,
id
:
item
.
id
,
}
)
)
state
.
loading
=
false
;
localStorage
.
setItem
(
‘listStudent’
,
JSON
.
stringify
(
state
)
)
;
return
state
;
}
,
//action này nhận nhiệm vụ, nhưng không làm gì,
//nghĩa là khi đi qua nó, middleware nhận ra nó thì sẽ làm việc gì đó khác
action_saga_getStudents
(
state
,
action
)
{
state
.
loading
=
true
;
return
state
;
}
,
}
}
)
;
// export ra các actions, reducer tương ứng 😀
const
{
action_saveStudent
,
action_deleteStudent
,
action_setListStudent
,
action_saga
_
getStudents
}
=
listStudent_slice
.
actions
;
const
listStudent_reducer
=
listStudent_slice
.
reducer
;
</script>
<
!
—
===
===
===
==
src
/
api
/
studentAPI
===
===
===
==
—
>
<script
type
=
“text/babel”
>
//API cần để riêng 1 file
// Hàm này được button dispatch lên, trong quá trình chạy, nó lại dispatch 1 action khác
const
getStudentsOnline
=
(
url
)
=
>
async
(
dispatch
)
=
>
{
console
.
log
(
“url: “
,
url
)
;
let
response
=
await
fetch
(
url
)
;
let
responseJSON
=
await
response
.
json
(
)
;
dispatch
(
action_setListStudent
(
responseJSON
)
)
;
}
</script>
<script>
// Các Effect Creator của saga hay gặp
// takeEvery: luôn chạy
// takeLatest: chạy action cuối, các action bị ghi đè sẽ cancel
// takeLeading: ngược lại, chạy action đầu, các action sau định ghi đè thì bị cancel
// debounce, throttle, fork, retry, put …
const
productSaga
=
function
*
(
)
{
//Các saga thông thường chỉ chạy 1 lần khi khởi động được rootSaga chạy
//Sử dụng takeEvery để lắng nghe liên tục và bắt các sự kiện khi có action gửi lên, chứ saga này không chạy lại nữa
// => (‘*’) => lắng nghe mọi action
// console.log(“Product saga”);
// yield ReduxSaga.effects.takeEvery(‘*’, handle_getProductsOnline)
}
const
handle_getStudentsOnline
=
function
*
(
action
)
{
console
.
log
(
‘Watting 1s’
,
action
)
;
yield
ReduxSaga
.
effects
.
delay
(
1000
)
console
.
log
(
‘Watiing done’
)
;
let
url
=
action
.
payload
;
let
response
=
yield
fetch
(
url
)
;
let
responseJSON
=
yield
response
.
json
(
)
;
yield
ReduxSaga
.
effects
.
put
(
action_setListStudent
(
responseJSON
)
)
}
const
studentSaga
=
function
*
(
)
{
//console.log(“student saga”);
// Liên tục lắng nghe 1 action và phản ứng với nó bằng takeEvery
// action.type được khai báo bằng 2 cách: dùng chuỗi: “reducer_name/action_function_name” hoặc “action_function().type”
// => Khi gặp action.type ==> chạy hàm phản ứng “logStudent”
// yield ReduxSaga.effects.takeEvery(‘listStudent/action_deleteStudent’, logStudent)
yield
ReduxSaga
.
effects
.
takeEvery
(
action_saga_getStudents
(
)
.
type
,
handle_getStudentsOnline
)
}
const
rootSaga
=
function
*
(
)
{
// console.log(“Root saga”);
// RootSaga sẽ khởi chạy tất cả các saga khác lần đầu tiên khi khởi động
yield
ReduxSaga
.
effects
.
all
(
[
productSaga
(
)
,
studentSaga
(
)
]
)
}
</script>
<
!
—
===
===
===
==
src
/
store
.
js
===
===
===
==
—
>
<script>
//Khai báo sagaMiddleware (xem thêm trên trang chủ cách khai báo khi sử dụng npm install)
const
sagaMiddleware
=
ReduxSaga
.
default
(
)
;
//Khai báo store với ReduxToolkit như ở dưới đây
//ReduxToolkit đã cài đặt sẵn middlewareThunk và Redux devtools extension rồi, ko cần cài đặt gì thêm nữa
const
store
=
RTK
.
configureStore
(
{
reducer
:
{
listStudent
:
listStudent_reducer
,
//product: product_reducer,
}
,
middleware
:
(
getDefaultMiddleware
)
=
>
// Có 3 middleware ban đầu của Redux-Saga (mặc định là true) (chưa test đc chức năng serializableCheck)
getDefaultMiddleware
(
{
thunk
:
true
,
serializableCheck
:
true
,
immutableCheck
:
true
,
}
)
.
concat
(
sagaMiddleware
)
,
//sài thêm “sagaMiddleware”
devTools
:
true
,
//default là true (có dùng devTools không)
}
)
// Khởi chạy rootSaga sau khi được mount vào Store
sagaMiddleware
.
run
(
rootSaga
)
;
</script>
<
!
—
===
===
===
==
components
/
App
.
js
===
===
===
==
—
>
<script
type
=
“text/babel”
>
function
App
(
props
)
{
//Sử dụng useSelector và useDispatch sẽ tiện hơn rất nhiều so với phải dùng HOC
const
useAppDispatch
=
ReactRedux
.
useDispatch
(
)
;
const
useAppSelector
=
ReactRedux
.
useSelector
;
const
listStudent
=
useAppSelector
(
state
=
>
state
.
listStudent
)
;
const
dispatch_saveStudent
=
(
student
)
=
>
useAppDispatch
(
action_saveStudent
(
student
)
)
;
const
dispatch_deleteStudent
=
(
id
)
=
>
useAppDispatch
(
action_deleteStudent
(
id
)
)
;
// Đoạn dưới là dispatch 1 function (function này là 1 hàm bất đồng bộ, nó chạy xong rồi lại dispatch 1 hàm khác)
const
dispatch_getStudentsOnline
=
(
url
)
=
>
useAppDispatch
(
getStudentsOnline
(
url
)
)
;
const
dispatch_saga_getStudents
=
(
url
)
=
>
useAppDispatch
(
action_saga_getStudents
(
url
)
)
;
const
[
url_fakeStudent
,
setUrl_fakeStudent
]
=
React
.
useState
(
‘https://jsonplaceholder.typicode.com/users’
)
const
[
filterName
,
setFilterName
]
=
React
.
useState
(
”
)
;
const
[
student
,
setStudent
]
=
React
.
useState
(
{
username
:
”
,
:
”
,
id
:
”
}
)
;
const
updateState_student
=
(
e
)
=
>
{
let
newStudent
=
{
.
.
.
student
}
;
newStudent
[
e
.
target
.
name
]
=
e
.
target
.
value
;
setStudent
(
newStudent
)
}
;
const
onSaveStudent
=
(
e
)
=
>
{
if
(
!
student
.
username
||
!
student
.
)
return
;
dispatch_saveStudent
(
student
)
;
setStudent
(
{
username
:
”
,
:
”
,
id
:
”
}
)
;
}
;
return
(
<
div
>
<
button
onClick
=
{
(
)
=
>
{
setStudent
(
{
username
:
”
,
:
”
,
id
:
”
}
)
}
}
>
New
Student
<
/
button
>
<
br
/
>
<
input
type
=
“text”
placeholder
=
“username”
name
=
“username”
value
=
{
student
.
username
}
onChange
=
{
updateState_student
}
/
>
<
br
/
>
<
input
type
=
“text”
placeholder
=
“email”
name
=
“email”
value
=
{
student
.
}
onChange
=
{
updateState_student
}
/
>
<
br
/
>
<
button
onClick
=
{
onSaveStudent
}
>
{
student
.
id
?
‘Update Student’
:
‘Add Student’
}
<
/
button
>
<
br
/
>
<
br
/
>
<
input
type
=
“text”
placeholder
=
“Search …”
name
=
“txtFilterName”
value
=
{
filterName
}
onChange
=
{
(
e
)
=
>
{
setFilterName
(
e
.
target
.
value
)
}
}
/
>
<
table
>
<
thead
>
<
tr
>
<
th
>
#
<
/
th
>
<
th
>
Username
<
/
th
>
<
th
>
<
/
th
>
<
th
>
Action
<
/
th
>
<
/
tr
>
<
/
thead
>
<
tbody
>
{
listStudent
.
data
.
filter
(
(
item
)
=
>
item
.
username
.
includes
(
filterName
)
)
.
map
(
(
item
,
index
)
=
>
{
return
(
<
tr
key
=
{
item
.
id
}
>
<
td
>
{
index
}
<
/
td
>
<
td
>
{
item
.
username
}
<
/
td
>
<
td
>
{
item
.
}
<
/
td
>
<
td
>
<
button
onClick
=
{
(
e
)
=
>
{
setStudent
(
{
.
.
.
item
}
)
}
}
>
S
ử
a
<
/
button
>
<
button
onClick
=
{
(
e
)
=
>
{
dispatch_deleteStudent
(
item
.
id
)
}
}
>
X
ó
a
<
/
button
>
<
/
td
>
<
/
tr
>
)
;
}
)
}
<
/
tbody
>
<
/
table
>
<
br
/
>
<
select
value
=
{
url_fakeStudent
}
onChange
=
{
(
e
)
=
>
{
setUrl_fakeStudent
(
e
.
target
.
value
)
}
}
>
<
option
value
=
“https://jsonplaceholder.typicode.com/users”
>
https
:
//jsonplaceholder.typicode.com/users</option>
<
option
value
=
“https://fakestoreapi.com/users?limit=10”
>
https
:
//fakestoreapi.com/users?limit=10</option>
<
/
select
>
<
br
/
>
<
button
onClick
=
{
e
=
>
{
dispatch_getStudentsOnline
(
url_fakeStudent
)
}
}
>
Get
Student
Online
<
/
button
>
<
br
/
>
<
button
onClick
=
{
e
=
>
{
dispatch_saga_getStudents
(
url_fakeStudent
)
}
}
>
Get
Student
Saga
<
/
button
>
{
listStudent
.
loading
&&
<
img
src
=
“https://vcdn-ione.vnecdn.net/2016/07/13/loading-256-0001-4566-1468383063.gif”
width
=
“12px”
/
>
}
<
/div>
);
}
</s
cript
>
<
!
—
===
===
===
==
src
/
index
.
js
===
===
===
==
—
>
<script
type
=
“text/babel”
>
//Connect React và Redux bằng Provider, với store=store đã được khai báo ở trên
const
Provider
=
ReactRedux
.
Provider
;
ReactDOM
.
render
(
<
Provider
store
=
{
store
}
>
<
App
/
>
<
/Provider>,
document.getElementById(‘root’)
);
</s
cript
>
<
/
body
>
<
/
html
>