Bạn không.
Nhưng ... bạn nên sử dụng redux-saga :)
Câu trả lời của Dan Abramov là đúng redux-thunk
nhưng tôi sẽ nói thêm một chút về redux-saga khá giống nhưng mạnh hơn.
Khai báo VS bắt buộc
- DOM : jQuery là bắt buộc / React là khai báo
- Monads : IO là mệnh lệnh / Miễn phí là khai báo
- Hiệu ứng Redux :
redux-thunk
là mệnh lệnh / redux-saga
là khai báo
Khi bạn có một cái rương trong tay, như một đơn vị IO hoặc một lời hứa, bạn không thể dễ dàng biết nó sẽ làm gì khi bạn thực hiện. Cách duy nhất để kiểm tra một thunk là thực thi nó và chế nhạo người điều phối (hoặc toàn bộ thế giới bên ngoài nếu nó tương tác với nhiều thứ hơn ...).
Nếu bạn đang sử dụng giả, thì bạn không làm lập trình chức năng.
Nhìn qua lăng kính của các hiệu ứng phụ, giả là một lá cờ cho thấy mã của bạn không trong sạch, và trong mắt của lập trình viên chức năng, bằng chứng rằng có gì đó không đúng. Thay vì tải xuống một thư viện để giúp chúng tôi kiểm tra tảng băng còn nguyên vẹn, chúng ta nên đi thuyền xung quanh nó. Một anh chàng TDD / Java khó tính đã từng hỏi tôi làm thế nào để bạn chế giễu trong Clojure. Câu trả lời là, chúng ta thường không. Chúng ta thường xem đó là một dấu hiệu chúng ta cần cấu trúc lại mã của mình.
Nguồn
Các sagas (như chúng đã được triển khai redux-saga
) là khai báo và giống như các thành phần Free monad hoặc React, chúng dễ dàng hơn để kiểm tra mà không cần giả.
Xem thêm bài viết này :
trong FP hiện đại, chúng ta không nên viết chương trình - chúng ta nên viết mô tả về các chương trình, sau đó chúng ta có thể hướng nội, biến đổi và diễn giải theo ý muốn.
(Trên thực tế, Redux-saga giống như một phép lai: dòng chảy là bắt buộc nhưng hiệu ứng là khai báo)
Nhầm lẫn: hành động / sự kiện / lệnh ...
Có rất nhiều sự nhầm lẫn trong thế giới frontend về cách một số khái niệm phụ trợ như CQRS / EventSource và Flux / Redux có thể liên quan với nhau, chủ yếu là vì trong Flux chúng ta sử dụng thuật ngữ "hành động" đôi khi có thể đại diện cho cả mã mệnh lệnh ( LOAD_USER
) và sự kiện ( USER_LOADED
). Tôi tin rằng giống như tìm nguồn cung ứng sự kiện, bạn chỉ nên gửi các sự kiện.
Sử dụng sagas trong thực tế
Hãy tưởng tượng một ứng dụng có liên kết đến hồ sơ người dùng. Cách thức thành ngữ để xử lý việc này với mỗi phần mềm trung gian sẽ là:
redux-thunk
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
Câu chuyện này dịch là:
mỗi khi tên người dùng được nhấp, hãy tìm nạp hồ sơ người dùng và sau đó gửi một sự kiện với hồ sơ được tải.
Như bạn có thể thấy, có một số lợi thế của redux-saga
.
Việc sử dụng takeLatest
giấy phép để thể hiện rằng bạn chỉ quan tâm để có được dữ liệu của tên người dùng cuối cùng được nhấp (xử lý các vấn đề tương tranh trong trường hợp người dùng nhấp rất nhanh vào nhiều tên người dùng). Loại công cụ này là khó khăn với thunks. Bạn có thể đã sử dụng takeEvery
nếu bạn không muốn hành vi này.
Bạn giữ người sáng tạo hành động tinh khiết. Lưu ý rằng vẫn hữu ích để giữ các ActionCreators (trong sagas put
và các thành phần dispatch
), vì nó có thể giúp bạn thêm xác thực hành động (xác nhận / luồng / bản thảo) trong tương lai.
Mã của bạn trở nên dễ kiểm tra hơn nhiều vì các hiệu ứng là khai báo
Bạn không cần phải kích hoạt các cuộc gọi giống như rpc nữa actions.loadUser()
. Giao diện người dùng của bạn chỉ cần gửi những gì ĐÃ XẢY RA. Chúng tôi chỉ bắn các sự kiện (luôn luôn ở thì quá khứ!) Và không hành động nữa. Điều này có nghĩa là bạn có thể tạo ra các "con vịt" tách rời hoặc Bối cảnh bị ràng buộc và câu chuyện có thể đóng vai trò là điểm khớp nối giữa các thành phần mô-đun này.
Điều này có nghĩa là các chế độ xem của bạn dễ quản lý hơn vì chúng không cần phải chứa lớp dịch đó giữa những gì đã xảy ra và những gì sẽ xảy ra như một hiệu ứng
Ví dụ, hãy tưởng tượng một chế độ xem cuộn vô hạn. CONTAINER_SCROLLED
có thể dẫn đến NEXT_PAGE_LOADED
, nhưng nó có thực sự là trách nhiệm của container có thể cuộn để quyết định xem chúng ta có nên tải trang khác hay không? Sau đó, anh ta phải nhận thức được những thứ phức tạp hơn như liệu trang cuối cùng đã được tải thành công hay chưa, nếu đã có một trang cố gắng tải, hoặc nếu không còn mục nào để tải nữa? Tôi không nghĩ vậy: để có thể sử dụng lại tối đa, thùng chứa có thể cuộn chỉ cần mô tả rằng nó đã được cuộn. Việc tải trang là "hiệu ứng kinh doanh" của cuộn đó
Một số người có thể lập luận rằng các máy phát điện vốn có thể ẩn trạng thái bên ngoài cửa hàng redux với các biến cục bộ, nhưng nếu bạn bắt đầu sắp xếp những thứ phức tạp bên trong thunks bằng cách bắt đầu bộ hẹn giờ, v.v ... bạn sẽ gặp vấn đề tương tự. Và có một select
hiệu ứng mà bây giờ cho phép để có được một số trạng thái từ cửa hàng Redux của bạn.
Sagas có thể được du hành thời gian và cũng cho phép ghi nhật ký dòng chảy và các công cụ phát triển phức tạp hiện đang được thực hiện. Dưới đây là một số ghi nhật ký luồng không đồng bộ đơn giản đã được triển khai:
Tách
Sagas không chỉ thay thế redux thunks. Họ đến từ phụ trợ / hệ thống phân tán / tìm nguồn cung ứng sự kiện.
Đó là một quan niệm sai lầm rất phổ biến rằng sagas chỉ ở đây để thay thế đàn hồi của bạn với khả năng kiểm tra tốt hơn. Trên thực tế đây chỉ là một chi tiết thực hiện của redux-saga. Sử dụng hiệu ứng khai báo tốt hơn thunks cho khả năng kiểm tra, nhưng mẫu saga có thể được triển khai trên đầu trang của mã bắt buộc hoặc khai báo.
Ở nơi đầu tiên, saga là một phần mềm cho phép điều phối các giao dịch chạy dài (tính nhất quán cuối cùng) và các giao dịch trên các bối cảnh bị ràng buộc khác nhau (biệt ngữ thiết kế hướng tên miền).
Để đơn giản hóa điều này cho thế giới frontend, hãy tưởng tượng có widget1 và widget2. Khi một số nút trên widget1 được nhấp, thì nó sẽ có hiệu lực trên widget2. Thay vì ghép 2 widget lại với nhau (ví dụ widget1 gửi một hành động nhắm vào widget2), widget1 chỉ gửi rằng nút của nó đã được bấm. Sau đó, saga lắng nghe nút này bấm và sau đó cập nhật widget2 bằng cách phân tán một sự kiện mới mà widget2 biết.
Điều này thêm một mức độ gián tiếp không cần thiết cho các ứng dụng đơn giản, nhưng làm cho nó dễ dàng hơn để mở rộng các ứng dụng phức tạp. Bây giờ bạn có thể xuất bản widget1 và widget2 lên các kho npm khác nhau để họ không bao giờ phải biết về nhau, mà không cần phải chia sẻ sổ đăng ký hành động toàn cầu. Hai widget hiện đang có bối cảnh giới hạn có thể sống riêng. Họ không cần nhau nhất quán và cũng có thể được sử dụng lại trong các ứng dụng khác. Câu chuyện là điểm kết nối giữa hai vật dụng phối hợp chúng theo cách có ý nghĩa cho doanh nghiệp của bạn.
Một số bài viết hay về cách cấu trúc ứng dụng Redux của bạn, trên đó bạn có thể sử dụng Redux-saga vì lý do tách rời:
Một usecase cụ thể: hệ thống thông báo
Tôi muốn các thành phần của mình có thể kích hoạt hiển thị thông báo trong ứng dụng. Nhưng tôi không muốn các thành phần của mình được kết hợp chặt chẽ với hệ thống thông báo có quy tắc kinh doanh riêng (tối đa 3 thông báo được hiển thị cùng lúc, xếp hàng thông báo, thời gian hiển thị 4 giây, v.v.).
Tôi không muốn các thành phần JSX của mình quyết định khi nào thông báo sẽ hiển thị / ẩn. Tôi chỉ cung cấp cho nó khả năng yêu cầu thông báo và để lại các quy tắc phức tạp bên trong câu chuyện. Loại công cụ này khá khó thực hiện với thunks hoặc lời hứa.
Tôi đã mô tả ở đây làm thế nào điều này có thể được thực hiện với saga
Tại sao nó được gọi là Saga?
Thuật ngữ saga xuất phát từ thế giới phụ trợ. Ban đầu tôi đã giới thiệu Yassine (tác giả của Redux-saga) cho thuật ngữ đó trong một cuộc thảo luận dài .
Ban đầu, thuật ngữ đó được giới thiệu bằng một tờ giấy , mẫu saga được cho là được sử dụng để xử lý tính nhất quán cuối cùng trong các giao dịch phân tán, nhưng việc sử dụng nó đã được các nhà phát triển phụ trợ mở rộng sang định nghĩa rộng hơn để giờ đây nó cũng bao trùm cả "trình quản lý quy trình" mẫu (bằng cách nào đó mẫu saga gốc là một hình thức quản lý quy trình chuyên biệt).
Ngày nay, thuật ngữ "saga" gây nhầm lẫn vì nó có thể mô tả 2 điều khác nhau. Vì nó được sử dụng trong redux-saga, nó không mô tả cách xử lý các giao dịch phân tán mà là cách phối hợp các hành động trong ứng dụng của bạn. redux-saga
cũng có thể được gọi redux-process-manager
.
Xem thêm:
Lựa chọn thay thế
Nếu bạn không thích ý tưởng sử dụng máy phát điện nhưng bạn quan tâm đến mẫu saga và các thuộc tính tách rời của nó, bạn cũng có thể đạt được điều tương tự với redux-epic
obsable sử dụng tên để mô tả chính xác mẫu tương tự, nhưng với RxJS. Nếu bạn đã quen thuộc với Rx, bạn sẽ cảm thấy như ở nhà.
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
Một số tài nguyên hữu ích redux-saga
Tư vấn năm 2017
- Đừng lạm dụng Redux-saga chỉ vì lợi ích của việc sử dụng nó. Các lệnh gọi API có thể kiểm tra chỉ không có giá trị.
- Đừng loại bỏ thunks khỏi dự án của bạn cho hầu hết các trường hợp đơn giản.
- Đừng ngần ngại gửi thunks
yield put(someActionThunk)
nếu nó có ý nghĩa.
Nếu bạn sợ sử dụng Redux-saga (hoặc có thể quan sát được Redux) nhưng chỉ cần mẫu tách rời, hãy kiểm tra đăng ký redux-gửi-đăng ký : nó cho phép nghe công văn và kích hoạt công văn mới trong trình nghe.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});