Tại sao chúng ta cần phần mềm trung gian cho luồng không đồng bộ trong Redux?

734
sbichenko 2016-01-03 11:09.

Theo tài liệu, "Không có phần mềm trung gian, cửa hàng Redux chỉ hỗ trợ luồng dữ liệu đồng bộ" . Tôi không hiểu tại sao lại như vậy. Tại sao thành phần vùng chứa không thể gọi API không đồng bộ và sau đó dispatchlà các hành động?

Ví dụ, hãy tưởng tượng một giao diện người dùng đơn giản: một trường và một nút. Khi người dùng nhấn nút, trường sẽ được điền dữ liệu từ máy chủ từ xa.

import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';

const ActionTypes = {
    STARTED_UPDATING: 'STARTED_UPDATING',
    UPDATED: 'UPDATED'
};

class AsyncApi {
    static getFieldValue() {
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                resolve(Math.floor(Math.random() * 100));
            }, 1000);
        });
        return promise;
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <input value={this.props.field}/>
                <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
                {this.props.isWaiting && <div>Waiting...</div>}
            </div>
        );
    }
}
App.propTypes = {
    dispatch: React.PropTypes.func,
    field: React.PropTypes.any,
    isWaiting: React.PropTypes.bool
};

const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
    switch (action.type) {
        case ActionTypes.STARTED_UPDATING:
            return { ...state, isWaiting: true };
        case ActionTypes.UPDATED:
            return { ...state, isWaiting: false, field: action.payload };
        default:
            return state;
    }
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
    (state) => {
        return { ...state };
    },
    (dispatch) => {
        return {
            update: () => {
                dispatch({
                    type: ActionTypes.STARTED_UPDATING
                });
                AsyncApi.getFieldValue()
                    .then(result => dispatch({
                        type: ActionTypes.UPDATED,
                        payload: result
                    }));
            }
        };
    })(App);
export default class extends React.Component {
    render() {
        return <Provider store={store}><ConnectedApp/></Provider>;
    }
}

Khi thành phần đã xuất được hiển thị, tôi có thể nhấp vào nút và đầu vào được cập nhật chính xác.

Lưu ý updatechức năng trong connectcuộc gọi. Nó gửi một hành động cho Ứng dụng biết rằng nó đang cập nhật, sau đó thực hiện một lệnh gọi không đồng bộ. Sau khi cuộc gọi kết thúc, giá trị đã cung cấp sẽ được gửi đi dưới dạng trọng tải của một hành động khác.

Có gì sai với cách tiếp cận này? Tại sao tôi muốn sử dụng Redux Thunk hoặc Redux Promise, như tài liệu gợi ý?

CHỈNH SỬA: Tôi đã tìm kiếm manh mối trong repo Redux và nhận thấy rằng trước đây Action Creators bắt buộc phải là những chức năng thuần túy. Ví dụ: đây là một người dùng đang cố gắng cung cấp giải thích tốt hơn cho luồng dữ liệu không đồng bộ:

Bản thân trình tạo hành động vẫn là một hàm thuần túy, nhưng hàm thunk mà nó trả về không cần phải như vậy và nó có thể thực hiện các lệnh gọi không đồng bộ của chúng ta

Người tạo hành động không còn bắt buộc phải trong sáng nữa. Vì vậy, phần mềm trung gian thunk / cam kết chắc chắn đã được yêu cầu trong quá khứ, nhưng có vẻ như điều này không còn đúng nữa?

11 answers

757
Dan Abramov 2016-01-05 10:50.

Có gì sai với cách tiếp cận này? Tại sao tôi muốn sử dụng Redux Thunk hoặc Redux Promise, như tài liệu gợi ý?

Không có gì sai với cách tiếp cận này. Nó chỉ bất tiện trong một ứng dụng lớn vì bạn sẽ có các thành phần khác nhau thực hiện các hành động giống nhau, bạn có thể muốn loại bỏ một số hành động hoặc giữ một số trạng thái cục bộ như ID tự động tăng dần gần với người tạo hành động, v.v. Vì vậy, nó chỉ dễ dàng hơn từ quan điểm duy trì để trích xuất người tạo hành động thành các chức năng riêng biệt.

Bạn có thể đọc Làm thế nào để gửi một hành động Redux với thời gian chờ? để biết hướng dẫn chi tiết hơn.

Phần mềm trung gian như Redux Thunk hoặc Redux Promise chỉ cung cấp cho bạn “đường cú pháp” để gửi lời hứa hoặc lời hứa, nhưng bạn không cần phải sử dụng nó.

Vì vậy, không có bất kỳ phần mềm trung gian nào, người tạo hành động của bạn có thể trông giống như

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

Nhưng với Thunk Middleware, bạn có thể viết nó như thế này:

// action creator
function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

Vì vậy, không có sự khác biệt lớn. Một điều tôi thích về cách tiếp cận thứ hai là thành phần không quan tâm rằng trình tạo hành động không đồng bộ. Nó chỉ gọi dispatchbình thường, nó cũng có thể sử dụng mapDispatchToPropsđể ràng buộc người tạo hành động đó bằng một cú pháp ngắn, v.v. Các thành phần không biết cách thực hiện người tạo hành động và bạn có thể chuyển đổi giữa các phương pháp tiếp cận không đồng bộ khác nhau (Redux Thunk, Redux Promise, Redux Saga ) mà không cần thay đổi các thành phần. Mặt khác, với cách tiếp cận rõ ràng trước đây, các thành phần của bạn biết chính xác rằng một lệnh gọi cụ thể là không đồng bộ và cần dispatchđược chuyển qua một số quy ước (ví dụ: dưới dạng tham số đồng bộ).

Cũng nghĩ về cách mã này sẽ thay đổi. Giả sử chúng tôi muốn có chức năng tải dữ liệu thứ hai và kết hợp chúng trong một trình tạo hành động duy nhất.

Với cách tiếp cận đầu tiên, chúng ta cần lưu ý đến loại người tạo hành động mà chúng ta đang kêu gọi:

// action creators
function loadSomeData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(dispatch, userId) {
  return Promise.all(
    loadSomeData(dispatch, userId), // pass dispatch first: it's async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


// component
componentWillMount() {
  loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}

Với Redux Thunk, người tạo hành động có thể dispatchlà kết quả của những người tạo hành động khác và thậm chí không nghĩ liệu những người tạo hành động đó là đồng bộ hay không đồng bộ:

// action creators
function loadSomeData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(userId) {
  return dispatch => Promise.all(
    dispatch(loadSomeData(userId)), // just dispatch normally!
    dispatch(loadOtherData(userId)) // just dispatch normally!
  );
}


// component
componentWillMount() {
  this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}

Với cách tiếp cận này, nếu sau này bạn muốn người tạo hành động của mình nhìn vào trạng thái Redux hiện tại, bạn có thể chỉ cần sử dụng getStateđối số thứ hai được truyền cho côn mà không cần sửa đổi mã gọi:

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (dispatch, getState) => {
    if (getState().data[userId].isLoaded) {
      return Promise.resolve();
    }

    fetch(`http://data.com/${userId}`)
      .then(res => res.json())
      .then(
        data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
        err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
      );
  }
}

Nếu bạn cần thay đổi nó để đồng bộ, bạn cũng có thể thực hiện việc này mà không cần thay đổi bất kỳ mã gọi điện nào:

// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
  return {
    type: 'LOAD_SOME_DATA_SUCCESS',
    data: localStorage.getItem('my-data')
  }
}

Vì vậy, lợi ích của việc sử dụng phần mềm trung gian như Redux Thunk hoặc Redux Promise là các thành phần không nhận thức được cách người tạo hành động được triển khai và liệu họ có quan tâm đến trạng thái Redux, cho dù chúng đồng bộ hay không đồng bộ và liệu họ có gọi người tạo hành động khác hay không . Nhược điểm của nó là hơi khó chuyển hướng, nhưng chúng tôi tin rằng nó đáng giá trong các ứng dụng thực tế.

Cuối cùng, Redux Thunk và bạn bè chỉ là một cách tiếp cận khả thi đối với các yêu cầu không đồng bộ trong ứng dụng Redux. Một cách tiếp cận thú vị khác là Redux Saga cho phép bạn xác định các daemon chạy dài (“sagas”) thực hiện các hành động khi chúng đến và chuyển đổi hoặc thực hiện các yêu cầu trước khi xuất các hành động. Điều này chuyển logic từ người tạo hành động thành sagas. Bạn có thể muốn xem nó và sau đó chọn những gì phù hợp với bạn nhất.

Tôi đã tìm kiếm manh mối của Redux repo và nhận thấy rằng trước đây Action Creators bắt buộc phải là những chức năng thuần túy.

Điều này là không chính xác. Các tài liệu nói điều này, nhưng các tài liệu đã sai.
Người tạo hành động không bao giờ bắt buộc phải là những người hoạt động thuần túy.
Chúng tôi đã sửa các tài liệu để phản ánh điều đó.

468
Sebastien Lorber 2016-01-06 14:43.

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-thunknhư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 mệnh lệnh

  • DOM : jQuery là bắt buộc / React là khai báo
  • Đơn nguyên : IO là bắt buộc / Miễn phí là khai báo
  • Hiệu ứng Redux : redux-thunklà bắt buộc / redux-sagalà khai báo

Khi bạn có trong tay một quyền lợi, chẳng hạn như đơn nguyên 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 cú đánh là thực hiện 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 mocks, thì bạn đang không lập trình chức năng.

Nhìn qua lăng kính của các tác dụng phụ, những trò giễu cợt là một dấu hiệu cho thấy mã của bạn không tinh khiết và trong mắt của lập trình viên chức năng, bằng chứng cho thấy có điều gì đó không ổn. 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 hay không, chúng tôi nên đi thuyền xung quanh nó. Một anh chàng TDD / Java khó tính đã từng hỏi tôi rằng bạn làm thế nào để chế nhạo trong Clojure. Câu trả lời là, chúng tôi thường không. Chúng tôi thường coi đó là dấu hiệu chúng tôi cần cấu trúc lại mã của mình.

Nguồn

Các sagas (khi chúng được triển khai redux-saga) có tính khai báo và giống như các thành phần Free monad hoặc React, chúng dễ kiểm tra hơn nhiều mà không cần bất kỳ mô hình nào.

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ể xem xét nội dung, biến đổi và diễn giải theo ý muốn.

(Trên thực tế, Redux-saga giống như một sự kết hợp: dòng chảy là bắt buộc nhưng các hiệu ứng mang tính 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 / EventSourcing và Flux / Redux có thể liên quan, chủ yếu là vì trong Flux, chúng tôi sử dụng thuật ngữ "action" mà đô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ành ngữ để xử lý điều 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 được dịch thành:

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ơ đã 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 các takeLatestgiấy phép để thể hiện rằng bạn chỉ quan tâm đến việc lấy dữ liệu của tên người dùng cuối cùng được nhấp vào (xử lý các vấn đề đồng thời 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 rất khó với côn đồ. Bạn có thể đã sử dụng takeEverynếu bạn không muốn hành vi này.

Bạn giữ cho người tạo hành động trong sáng. Lưu ý rằng nó vẫn hữu ích nếu giữ actionCreators (trong sagas putvà các thành phần dispatch), vì nó có thể giúp bạn thêm xác thực hành động (khẳng định / luồng / typecript) trong tương lai.

Mã của bạn trở nên dễ kiểm tra hơn nhiều khi các hiệu ứng được khai báo

Bạn không cần phải kích hoạt các cuộc gọi giống rpc như thế actions.loadUser()nữa. Giao diện người dùng của bạn chỉ cần gửi những gì ĐÃ XẢY RA. Chúng ta chỉ kích hoạt các sự kiện (luôn ở thì quá khứ!) Chứ không phải các hành động nữa. Điều này có nghĩa là bạn có thể tạo các "vịt" hoặc Bối cảnh giới hạn được tách rời và saga có thể hoạt động như điểm kết hợp 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 đó nữa 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_SCROLLEDcó thể dẫn đến NEXT_PAGE_LOADED, nhưng có thực sự trách nhiệm của vùng chứa có thể cuộn là quyết định xem chúng ta có nên tải một trang khác hay không? Sau đó, anh ta phải để ý đến 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 hoặc đã có một trang đang cố gắng tải hay không hoặc không còn mục nào để tải? Tôi không nghĩ vậy: để có khả năng tái sử dụng tối đa, vùng chứa có thể cuộn chỉ nên mô tả rằng nó đã được cuộn. Việc tải một trang là "hiệu quả kinh doanh" của cuộn đó

Một số người có thể tranh luận rằng trình tạo vốn có thể ẩn trạng thái bên ngoài cửa hàng redux bằng 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 khởi động bộ định thời, v.v. thì bạn cũng sẽ gặp vấn đề tương tự. Và có một selecthiệu ứng 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ể du hành thời gian và cũng cho phép ghi nhật ký luồng phức tạp và các công cụ dành cho nhà phát triển hiện đang được phát triể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 côn đồ. Chúng đến từ phụ trợ / hệ thống phân tán / nguồn cung ứng sự kiện.

Một quan niệm sai lầm rất phổ biến rằng sagas chỉ ở đây để thay thế các côn redux của bạn với khả năng kiểm tra tốt hơn. Thực ra đây chỉ là một chi tiết triển khai của redux-saga. Sử dụng các hiệu ứng khai báo tốt hơn so với thu nhận để có thể kiểm tra, nhưng mẫu saga có thể được triển khai trên đầu mã mệnh lệnh hoặc mã khai báo.

Trước hết, saga là một phần mềm cho phép điều phối các giao dịch đang chạy lâu 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 giới hạn khác nhau (biệt ngữ thiết kế hướng 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 vào, thì nó sẽ có tác động đến widget2. Thay vì kết hợp 2 widget với nhau (tức là widget1 thực hiện một hành động nhắm mục tiêu widget2), widget1 chỉ thông báo rằng nút của nó đã được nhấp. Sau đó, saga lắng nghe cho lần nhấp vào nút này và sau đó cập nhật widget2 bằng cách loại bỏ một sự kiện mới mà widget2 biết.

Điều này bổ sung một mức độ điều hướng không cần thiết đối với các ứng dụng đơn giản, nhưng làm cho việc mở rộng các ứng dụng phức tạp trở nên dễ dàng hơn. Giờ đây, bạn có thể xuất bản widget1 và widget2 lên các kho lưu trữ npm khác nhau để chúng không bao giờ phải biết về nhau mà không cần chúng phải chia sẻ sổ đăng ký toàn cầu về các hành động. 2 widget hiện là các ngữ cảnh giới hạn có thể tồn tại riêng biệt. Chúng không cần phải nhất quán với nhau 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 hợp giữa hai vật dụng giúp điều phối 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 để tách các lý do:

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 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 một 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 saga. Loại công cụ này khá khó thực hiện với những lời côn đồ hoặc hứa hẹn.

Tôi đã mô tả ở đây cách đ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) về thuật ngữ đó trong một cuộc thảo luận dài .

Ban đầu, thuật ngữ đó được giới thiệu với một bài báo , 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 cách sử dụng của nó đã được các nhà phát triển phụ trợ mở rộng sang một định nghĩa rộng hơn để bây giờ nó cũng bao gồm "trình quản lý quy trình" (bằng cách nào đó, mô hình saga ban đầu là một hình thức quản lý quy trình chuyên biệt).

Ngày nay, thuật ngữ "saga" là khó hiểu 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à một cách để điều phối các hành động trong ứng dụng của bạn. redux-sagacũng có thể đã được gọi redux-process-manager.

Xem thêm:

Giải pháp thay thế

Nếu bạn không thích ý tưởng sử dụng máy phát điện nhưng lại hứng thú với mẫu saga và đặc tính tách của nó, bạn cũng có thể đạt được điều tương tự với redux-epic Observable sử dụng tên để mô tả chính xác cùng một mẫu, nhưng với RxJS. Nếu bạn đã quen thuộc với Rx, bạn sẽ cảm thấy thoải mái 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 của redux-saga

2017 khuyên

  • Đừng lạm dụng Redux-saga chỉ vì mục đích sử dụng nó. Chỉ các lệnh gọi API có thể kiểm tra là không đáng.
  • Đừng loại bỏ côn đồ khỏi dự án của bạn đối với hầu hết các trường hợp đơn giản.
  • Đừng ngần ngại cử côn đồ đến yield put(someActionThunk)nếu nó có ý nghĩa.

Nếu bạn sợ sử dụng Redux-saga (hoặc Redux-Observable) nhưng chỉ cần mẫu tách, hãy kiểm tra redux-accept-subscribe : nó cho phép lắng nghe các công văn và kích hoạt các công văn mới trong trình lắng nghe.

const unsubscribe = store.addDispatchListener(action => {
  if (action.type === 'ping') {
    store.dispatch({ type: 'pong' });
  }
});
34
acjay 2016-01-04 17:28.

Câu trả lời ngắn gọn : có vẻ như là một cách tiếp cận hoàn toàn hợp lý đối với vấn đề không đồng bộ đối với tôi. Với một vài lưu ý.

Tôi đã có một dòng suy nghĩ rất giống nhau khi làm việc trong một dự án mới mà chúng tôi vừa bắt đầu công việc của tôi. Tôi là một fan hâm mộ lớn của hệ thống trang nhã của vani Redux để cập nhật cửa hàng và kết xuất các thành phần theo cách không cần thiết của cây thành phần React. Đối với tôi, có vẻ kỳ lạ khi mắc vào dispatchcơ chế thanh lịch đó để xử lý sự không đồng bộ.

Tôi đã kết thúc với một cách tiếp cận thực sự tương tự với những gì bạn có trong một thư viện mà tôi đã tính toán từ dự án của chúng tôi, mà chúng tôi gọi là react-redux-controller .

Tôi đã không đi đúng với cách tiếp cận chính xác mà bạn có ở trên vì một số lý do:

  1. Theo cách bạn viết, các chức năng điều phối đó không có quyền truy cập vào cửa hàng. Bạn có thể phần nào giải quyết vấn đề đó bằng cách để các thành phần giao diện người dùng của bạn chuyển tất cả thông tin mà chức năng điều phối cần. Nhưng tôi cho rằng điều này kết hợp các thành phần UI đó với logic điều phối một cách không cần thiết. Và vấn đề hơn, không có cách nào rõ ràng để chức năng điều phối truy cập trạng thái cập nhật trong các liên tục không đồng bộ.
  2. Các chức năng điều phối có quyền truy cập vào dispatchchính nó thông qua phạm vi từ vựng. Điều này giới hạn các tùy chọn để cấu trúc lại khi connectcâu lệnh đó vượt ra khỏi tầm tay - và nó trông khá khó sử dụng chỉ với một updatephương pháp đó. Vì vậy, bạn cần một số hệ thống cho phép bạn soạn các chức năng điều phối đó nếu bạn chia chúng thành các mô-đun riêng biệt.

Cùng với nhau, bạn phải thiết lập một số hệ thống để cho phép dispatchvà cửa hàng được đưa vào các chức năng điều phối của bạn, cùng với các thông số của sự kiện. Tôi biết ba cách tiếp cận hợp lý để tiêm phụ thuộc này:

  • redux-thunk thực hiện điều này theo một cách chức năng, bằng cách chuyển chúng vào các đòn thu của bạn (khiến chúng không hoàn toàn chính xác, theo định nghĩa mái vòm). Tôi chưa làm việc với các dispatchphương pháp tiếp cận phần mềm trung gian khác , nhưng tôi cho rằng về cơ bản chúng giống nhau.
  • react-redux-controller thực hiện điều này bằng một quy trình. Như một phần thưởng, nó cũng cung cấp cho bạn quyền truy cập vào "bộ chọn", là các hàm mà bạn có thể đã chuyển vào làm đối số đầu tiên connect, thay vì phải làm việc trực tiếp với cửa hàng thô, chuẩn hóa.
  • Bạn cũng có thể làm điều đó theo cách hướng đối tượng bằng cách đưa chúng vào thisngữ cảnh, thông qua nhiều cơ chế khả thi.

Cập nhật

Tôi nhận ra rằng một phần của câu hỏi hóc búa này là hạn chế của react-redux . Đối số đầu tiên để connectnhận ảnh chụp nhanh trạng thái, nhưng không phải là công văn. Đối số thứ hai nhận được công văn nhưng không nhận được trạng thái. Không đối số nào nhận được một cú đánh đóng lại trạng thái hiện tại, vì có thể xem trạng thái được cập nhật tại thời điểm tiếp tục / gọi lại.

26
XML 2017-03-06 16:46.

Mục tiêu của Abramov - và lý tưởng của mọi người - chỉ đơn giản là gói gọn sự phức tạp (và các lệnh gọi không đồng bộ) ở nơi thích hợp nhất .

Đâu là nơi tốt nhất để làm điều đó trong luồng dữ liệu Redux tiêu chuẩn? Làm thế nào về:

  • Bộ giảm tốc ? Không đời nào. Chúng phải là các chức năng thuần túy không có tác dụng phụ. Cập nhật cửa hàng là công việc nghiêm túc, phức tạp. Đừng làm ô nhiễm nó.
  • Thành phần Chế độ xem câm? Chắc chắn là không. Họ có một mối quan tâm: trình bày và tương tác với người dùng, và phải càng đơn giản càng tốt.
  • Thành phần container? Có thể, nhưng không tối ưu. Nó có ý nghĩa ở chỗ vùng chứa là nơi chúng ta đóng gói một số phức tạp liên quan đến chế độ xem và tương tác với cửa hàng, nhưng:
    • Các vùng chứa cần phải phức tạp hơn các thành phần câm, nhưng nó vẫn có một trách nhiệm duy nhất: cung cấp các ràng buộc giữa chế độ xem và trạng thái / cửa hàng. Logic không đồng bộ của bạn là một mối quan tâm hoàn toàn riêng biệt với điều đó.
    • Bằng cách đặt nó trong một vùng chứa, bạn sẽ khóa logic không đồng bộ của mình vào một ngữ cảnh duy nhất, cho một chế độ xem / tuyến đường. Ý kiến ​​tồi. Lý tưởng nhất là tất cả đều có thể tái sử dụng và hoàn toàn tách rời.
  • S ome Mô-đun dịch vụ khác? Ý tưởng tồi: bạn cần phải đưa quyền truy cập vào cửa hàng, đây là một cơn ác mộng về khả năng bảo trì / khả năng kiểm tra. Tốt hơn nên sử dụng phần mềm của Redux và chỉ truy cập vào cửa hàng bằng cách sử dụng các API / mô hình được cung cấp.
  • Các hành động và phần mềm trung gian giải thích chúng? Tại sao không?! Đối với người mới bắt đầu, đó là lựa chọn chính duy nhất mà chúng tôi còn lại. :-) Hợp lý hơn, hệ thống hành động là logic thực thi tách rời mà bạn có thể sử dụng từ mọi nơi. Nó có quyền truy cập vào cửa hàng và có thể thực hiện nhiều hành động hơn. Nó có một trách nhiệm duy nhất là tổ chức luồng kiểm soát và dữ liệu xung quanh ứng dụng, và hầu hết các bất đồng bộ đều phù hợp với điều đó.
    • Còn những Người sáng tạo hành động thì sao? Tại sao không chỉ làm async trong đó, thay vì trong chính các hành động và trong Middleware?
      • Đầu tiên và quan trọng nhất, người sáng tạo không có quyền truy cập vào cửa hàng, như phần mềm trung gian. Điều đó có nghĩa là bạn không thể gửi các hành động ngẫu nhiên mới, không thể đọc từ cửa hàng để soạn không đồng bộ của mình, v.v.
      • Vì vậy, hãy giữ sự phức tạp ở một nơi cần thiết phức tạp và giữ cho mọi thứ khác đơn giản. Các trình tạo sau đó có thể là các chức năng đơn giản, tương đối thuần túy, dễ kiểm tra.
18
Michelle Tilley 2016-01-04 20:20.

Để trả lời câu hỏi được đặt ra trong đầu:

Tại sao thành phần vùng chứa không thể gọi API không đồng bộ và sau đó gửi các hành động?

Hãy nhớ rằng những tài liệu đó dành cho Redux, không phải Redux cộng với React. Các cửa hàng Redux được kết nối với các thành phần React có thể thực hiện chính xác những gì bạn nói, nhưng một cửa hàng Plain Jane Redux không có phần mềm trung gian không chấp nhận các đối số dispatchngoại trừ các đối tượng đơn giản.

Không có phần mềm trung gian, tất nhiên bạn vẫn có thể làm được

const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));

Nhưng đó là một trường hợp tương tự mà không đồng pha được bao bọc xung quanh Redux chứ không phải xử lý bằng cách Redux. Vì vậy, phần mềm trung gian cho phép sự không đồng bộ bằng cách sửa đổi những gì có thể được truyền trực tiếp đến dispatch.


Điều đó nói rằng, tinh thần đề xuất của bạn, tôi nghĩ là có giá trị. Chắc chắn có những cách khác mà bạn có thể xử lý sự không đồng bộ trong ứng dụng Redux + React.

Một lợi ích của việc sử dụng phần mềm trung gian là bạn có thể tiếp tục sử dụng trình tạo hành động như bình thường mà không cần lo lắng về cách họ kết nối chính xác. Ví dụ: bằng cách sử dụng redux-thunk, mã bạn đã viết sẽ trông giống như

function updateThing() {
  return dispatch => {
    dispatch({
      type: ActionTypes.STARTED_UPDATING
    });
    AsyncApi.getFieldValue()
      .then(result => dispatch({
        type: ActionTypes.UPDATED,
        payload: result
      }));
  }
}

const ConnectedApp = connect(
  (state) => { ...state },
  { update: updateThing }
)(App);

trông không khác gì so với bản gốc - nó chỉ bị xáo trộn một chút - và connectkhông biết điều đó updateThinglà (hoặc cần) không đồng bộ.

Nếu bạn cũng muốn hỗ trợ những người tạo hành động hứa hẹn , có thể quan sát , sagas hoặc tùy chỉnh điên rồcó tính tuyên bố cao , thì Redux có thể làm điều đó chỉ bằng cách thay đổi những gì bạn chuyển đến dispatch(hay còn gọi là những gì bạn trả lại từ người tạo hành động). Không cần sử dụng các thành phần React (hoặc connectlệnh gọi).

11
Alireza 2017-11-19 02:22.

OK, chúng ta hãy bắt đầu xem phần mềm trung gian hoạt động như thế nào trước, câu trả lời khá chính xác cho câu hỏi, đây là mã nguồn của một hàm pplyMiddleWare trong Redux:

function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

Nhìn vào phần này, xem công văn của chúng ta trở thành một chức năng như thế nào .

  ...
  getState: store.getState,
  dispatch: function dispatch(action) {
  return _dispatch(action);
}
  • Lưu ý rằng mỗi phần mềm trung gian sẽ được cung cấp các chức năng dispatchgetStatenhư là các đối số được đặt tên.

OK, đây là cách Redux-thunk là một trong những phần mềm trung gian được sử dụng nhiều nhất cho Redux tự giới thiệu:

Phần mềm trung gian Redux Thunk cho phép bạn viết các trình tạo hành động trả về một hàm thay vì một hành động. Cú đánh có thể được sử dụng để trì hoãn việc thực hiện một hành động hoặc chỉ để gửi đi nếu một điều kiện nhất định được đáp ứng. Hàm bên trong nhận các phương thức lưu trữ gửi và getState làm tham số.

Vì vậy, như bạn thấy, nó sẽ trả về một hàm thay vì một hành động, có nghĩa là bạn có thể đợi và gọi nó bất cứ lúc nào bạn muốn vì nó là một hàm ...

Vậy cái quái gì thế này? Đó là cách nó được giới thiệu trong Wikipedia:

Trong lập trình máy tính, thunk là một chương trình con được sử dụng để đưa một phép tính bổ sung vào một chương trình con khác. Thunks chủ yếu được sử dụng để trì hoãn một phép tính cho đến khi nó cần thiết, hoặc để chèn các hoạt động vào đầu hoặc cuối của chương trình con khác. Chúng có nhiều ứng dụng khác để biên dịch mã tạo và lập trình mô-đun.

Thuật ngữ này có nguồn gốc là một dẫn xuất nói chung của "think".

Một hàm là một hàm bao bọc một biểu thức để trì hoãn việc đánh giá nó.

//calculation of 1 + 2 is immediate 
//x === 3 
let x = 1 + 2;

//calculation of 1 + 2 is delayed 
//foo can be called later to perform the calculation 
//foo is a thunk! 
let foo = () => 1 + 2;

Vì vậy, hãy xem khái niệm này dễ dàng như thế nào và nó có thể giúp bạn quản lý các hành động không đồng bộ của mình như thế nào ...

Đó là thứ bạn có thể sống mà không có nó, nhưng hãy nhớ trong lập trình luôn có những cách tốt hơn, gọn gàng hơn và đúng cách để thực hiện mọi thứ ...

5
SM Chinna 2018-02-10 02:03.

Để sử dụng Redux-saga là phần mềm trung gian tốt nhất trong việc triển khai React-redux.

Ví dụ: store.js

  import createSagaMiddleware from 'redux-saga';
  import { createStore, applyMiddleware } from 'redux';
  import allReducer from '../reducer/allReducer';
  import rootSaga from '../saga';

  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(
     allReducer,
     applyMiddleware(sagaMiddleware)
   )

   sagaMiddleware.run(rootSaga);

 export default store;

Và sau đó saga.js

import {takeLatest,delay} from 'redux-saga';
import {call, put, take, select} from 'redux-saga/effects';
import { push } from 'react-router-redux';
import data from './data.json';

export function* updateLesson(){
   try{
       yield put({type:'INITIAL_DATA',payload:data}) // initial data from json
       yield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js 
   }
   catch(e){
      console.log("error",e)
     }
  }

export function* updateDetail(action) {
  try{
       //To write store update details
   }  
    catch(e){
       console.log("error",e)
    } 
 }

export default function* rootSaga(){
    yield [
        updateLesson()
       ]
    }

Và sau đó action.js

 export default function updateFruit(props,fruit) {
    return (
       {
         type:"UPDATE_DETAIL",
         payload:fruit,
         props:props
       }
     )
  }

Và sau đó Reduceer.js

import {combineReducers} from 'redux';

const fetchInitialData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
 const updateDetailsData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
const allReducers =combineReducers({
   data:fetchInitialData,
   updateDetailsData
 })
export default allReducers; 

Và sau đó là main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/App.jsx';
import {Provider} from 'react-redux';
import store from './app/store';
import createRoutes from './app/routes';

const initialState = {};
const store = configureStore(initialState, browserHistory);

ReactDOM.render(
       <Provider store={store}>
          <App />  /*is your Component*/
       </Provider>, 
document.getElementById('app'));

thử cái này .. đang hoạt động

4
Daniel 2019-02-21 14:47.

Có những người tạo hành động đồng bộ và sau đó có những người tạo hành động không đồng bộ.

Trình tạo hành động đồng bộ là trình tạo hành động đồng bộ mà khi chúng ta gọi nó, nó ngay lập tức trả về đối tượng Hành động với tất cả dữ liệu có liên quan được đính kèm với đối tượng đó và nó sẵn sàng được xử lý bởi bộ giảm của chúng ta.

Trình tạo hành động không đồng bộ là trình tạo hành động sẽ cần một chút thời gian trước khi sẵn sàng gửi một hành động cuối cùng.

Theo định nghĩa, bất cứ khi nào bạn có người tạo hành động đưa ra yêu cầu mạng, thì người đó sẽ luôn đủ điều kiện là người tạo hành động không đồng bộ.

Nếu bạn muốn có những người tạo hành động không đồng bộ bên trong ứng dụng Redux, bạn phải cài đặt một thứ gọi là phần mềm trung gian cho phép bạn xử lý những người tạo hành động không đồng bộ đó.

Bạn có thể xác minh điều này trong thông báo lỗi cho chúng tôi biết sử dụng phần mềm trung gian tùy chỉnh cho các hành động không đồng bộ.

Vậy phần mềm trung gian là gì và tại sao chúng ta cần nó cho luồng không đồng bộ trong Redux?

Trong bối cảnh của phần mềm trung gian redux chẳng hạn như redux-thunk, phần mềm trung gian giúp chúng ta đối phó với những người tạo hành động không đồng bộ vì đó là thứ mà Redux không thể xử lý được.

Với phần mềm trung gian được tích hợp vào chu trình Redux, chúng tôi vẫn đang kêu gọi những người tạo hành động, tức là sẽ trả lại một hành động sẽ được thực hiện nhưng bây giờ khi chúng tôi gửi một hành động, thay vì gửi trực tiếp hành động đó đến tất cả các trình giảm bớt của chúng tôi, chúng tôi sẽ để nói rằng một hành động sẽ được gửi qua tất cả các phần mềm trung gian khác nhau bên trong ứng dụng.

Bên trong một ứng dụng Redux duy nhất, chúng ta có thể có nhiều hoặc ít phần mềm trung gian tùy thích. Phần lớn, trong các dự án chúng tôi làm việc, chúng tôi sẽ có một hoặc hai phần mềm trung gian được kết nối với cửa hàng Redux của chúng tôi.

Phần mềm trung gian là một hàm JavaScript đơn giản sẽ được gọi với mọi hành động mà chúng tôi thực hiện. Bên trong chức năng đó, phần mềm trung gian có cơ hội ngăn một hành động được gửi đến bất kỳ bộ giảm nào, nó có thể sửa đổi một hành động hoặc chỉ làm rối tung một hành động theo bất kỳ cách nào mà bạn ví dụ, chúng ta có thể tạo một phần mềm trung gian để điều khiển ghi nhật ký mọi hành động bạn thực hiện chỉ vì niềm vui xem của bạn.

Có rất nhiều phần mềm trung gian mã nguồn mở mà bạn có thể cài đặt làm phụ thuộc vào dự án của mình.

Bạn không bị giới hạn trong việc chỉ sử dụng phần mềm trung gian nguồn mở hoặc cài đặt chúng dưới dạng phụ thuộc. Bạn có thể viết phần mềm trung gian tùy chỉnh của riêng mình và sử dụng nó bên trong cửa hàng Redux của bạn.

Một trong những cách sử dụng phổ biến hơn của phần mềm trung gian (và nhận được câu trả lời của bạn) là để xử lý những người tạo hành động không đồng bộ, có lẽ phần mềm trung gian phổ biến nhất hiện có là redux-thunk và nó giúp bạn đối phó với những người tạo hành động không đồng bộ.

Có nhiều loại phần mềm trung gian khác cũng giúp bạn đối phó với những người tạo hành động không đồng bộ.

4
Mselmi Ali 2019-08-14 02:24.

Để trả lời câu hỏi:

Tại sao thành phần vùng chứa không thể gọi API không đồng bộ và sau đó gửi các hành động?

Tôi sẽ nói vì ít nhất hai lý do:

Lý do đầu tiên là sự tách biệt của các mối quan tâm, không phải là công việc của việc action creatorgọi apivà lấy lại dữ liệu, bạn phải chuyển hai đối số cho của bạn action creator function, the action typevà a payload.

Lý do thứ hai là vì redux stoređang đợi một đối tượng thuần túy với kiểu hành động bắt buộc và tùy chọn là a payload(nhưng ở đây bạn cũng phải vượt qua trọng tải).

Trình tạo hành động phải là một đối tượng đơn giản như sau:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

Và công việc của Redux-Thunk midlewaređể dispachekết quả của bạn api callcho phù hợp action.

3
Feras 2020-05-16 20:54.

Khi làm việc trong một dự án doanh nghiệp, có nhiều yêu cầu có sẵn trong phần mềm trung gian chẳng hạn như (saga) không có sẵn trong quy trình không đồng bộ đơn giản, dưới đây là một số:

  • Chạy yêu cầu song song
  • Thực hiện các hành động trong tương lai mà không cần chờ đợi
  • Cuộc gọi không chặn Hiệu ứng cuộc đua, lấy ví dụ trước
  • phản hồi để bắt đầu quy trình Sắp xếp các nhiệm vụ của bạn (trước tiên trong lần gọi đầu tiên)
  • Sáng tác
  • Hủy nhiệm vụ Tự động xóa nhiệm vụ.
  • Hỗ trợ Concurrency Running Saga bên ngoài phần mềm trung gian redux.
  • Sử dụng các kênh

Danh sách dài chỉ cần xem lại phần nâng cao trong tài liệu saga

0
coder9833idls 2020-09-09 00:31.

Redux không thể trả về một hàm thay vì một hành động. Đó chỉ là một sự thật. Đó là lý do tại sao mọi người sử dụng Thunk. Đọc 14 dòng mã này để xem cách nó cho phép chu trình không đồng bộ hoạt động với một số lớp chức năng được bổ sung:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

https://github.com/reduxjs/redux-thunk

Related questions

MORE COOL STUFF

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett đã bất chấp những lời khuyên hẹn hò điển hình khi cô gặp chồng mình.

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Michael Sheen là một diễn viên phi lợi nhuận nhưng chính xác thì điều đó có nghĩa là gì?

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Ngôi sao của Hallmark Colin Egglesfield chia sẻ về những cuộc gặp gỡ với người hâm mộ ly kỳ tại RomaDrama Live! cộng với chương trình INSPIRE của anh ấy tại đại hội.

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Bạn sẽ phải phủi sạch đầu đĩa Blu-ray hoặc DVD để xem tại sao Northern Exposure trở thành một trong những chương trình nổi tiếng nhất của thập niên 90.

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

8 công dụng tuyệt vời của Baking Soda và Giấm

8 công dụng tuyệt vời của Baking Soda và Giấm

Bạn biết đấy, hai sản phẩm này là nguồn điện để làm sạch, riêng chúng. Nhưng cùng với nhau, chúng có một loạt công dụng hoàn toàn khác.

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Thủy điện rất cần thiết cho lưới điện của Hoa Kỳ, nhưng nó chỉ tạo ra năng lượng khi có nước di chuyển. Bao nhiêu nhà máy thủy điện có thể gặp nguy hiểm khi các hồ và sông cạn kiệt?

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Tóc tỉa từ các tiệm và các khoản quyên góp cá nhân có thể được tái sử dụng như những tấm thảm thấm dầu và giúp bảo vệ môi trường.

Tận dụng lợi thế của việc bán hàng nhân Ngày của Cha này

Tận dụng lợi thế của việc bán hàng nhân Ngày của Cha này

Màn hình Samsung Galaxy S9 Plus. Chủ nhật này là Ngày của Cha⁠ — trong trường hợp nó khiến bạn suy nghĩ — và thay vì mua cho anh ấy một chiếc cà vạt trong năm nay, có lẽ đã đến lúc bạn mua cho anh ấy thứ mà anh ấy sẽ thực sự sử dụng.

Assassin's Creed Snuck Into Monster Hunter: World Last Night

Assassin's Creed Snuck Into Monster Hunter: World Last Night

Monster Hunter: World yêu thích các sự kiện chéo. Dù có nghĩa là hóa trang thành Dante của Devil May Cry, giả dạng Horizon: Zero Dawn's Aloy, hay chiến đấu với quái vật Final Fantasy, các nhiệm vụ sự kiện khác nhau của Thế giới được nâng cấp tự do so với các trò chơi khác.

Ava DuVernay Có Quà Tặng Ngày Của Mẹ cho Tất Cả Chúng Ta: Nếp Nhăn Thời Gian Sẽ Được Viết Lại Vào Cuối Tuần Ngày Của Mẹ!

Ava DuVernay Có Quà Tặng Ngày Của Mẹ cho Tất Cả Chúng Ta: Nếp Nhăn Thời Gian Sẽ Được Viết Lại Vào Cuối Tuần Ngày Của Mẹ!

Storm Reid, Oprah Winfrey, Mindy Kaling, Reese Witherspoon và Ava DuVernay tại buổi chiếu đặc biệt của A Wrinkle in Time tại Nhà hát Walter Reade ở Thành phố New York vào ngày 7 tháng 3 năm 2018 “Ava rất mong được nói chuyện với bạn,” một trong những người của Array dư luận viên nói qua điện thoại. (Array là tập thể phân phối, nghệ thuật và vận động chính sách của Ava DuVernay tập trung vào các bộ phim của người da màu và phụ nữ.

Nhiệm vụ bất khả thi 5 sẽ khôi phục niềm tin của bạn trong phim hành động Tentpole

Nhiệm vụ bất khả thi 5 sẽ khôi phục niềm tin của bạn trong phim hành động Tentpole

Mission Impossible: Rogue Nation bắt đầu ở một cấp độ khác. Theo nghĩa đen.

Edwin McCain ra mắt Grand Ole Opry: Quay cảnh hậu trường với nhạc sĩ 'I'll Be'

Edwin McCain ra mắt Grand Ole Opry: Quay cảnh hậu trường với nhạc sĩ 'I'll Be'

McCain, người đang làm việc cho một album mới, lần đầu tiên bước vào vòng kết nối vào tối thứ Sáu ở Nashville

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Rothschild's luggage got lost, but luckily she has an incredible closet to shop: Sister Paris Hilton's!

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa. Từ Hollywood đến New York và mọi nơi ở giữa, hãy xem các ngôi sao yêu thích của bạn đang làm gì!

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

Các nhà điều tra đang xem xét liệu nhóm và nghi phạm có biết nhau trước vụ tấn công hay không

Tôi viết như thế nào

Tôi viết như thế nào

Đối với tôi, mọi thứ là về dòng đầu tiên đó và nó sẽ đưa bạn đến đâu. Một số nhà văn bị điều khiển bởi cốt truyện, sự sắp xếp tinh tế của các quân cờ, trong khi những người khác bị lôi cuốn bởi một nhân vật và khả năng thực hiện một cuộc hành trình với một người bạn hư cấu mới.

Đường băng hạ cánh

Đường băng hạ cánh

Cuối hè đầu thu là mùa hoài niệm. Những chiếc đèn đường chiếu ánh sáng của chúng qua những con đường đẫm mưa, và những chiếc lá dưới chân - màu đỏ cam tắt trong bóng chạng vạng - là lời nhắc nhở về những ngày đã qua.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Vào năm 2021, tôi khuyến khích bạn suy nghĩ lại mọi thứ bạn biết về khách hàng mà bạn phục vụ và những câu chuyện bạn kể cho họ. Lùi lại.

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Vào ngày sinh nhật thứ 9 của Felix The Cat, tôi nhớ về một trong những mất mát lớn nhất trong cuộc đời trưởng thành của tôi - Sophie của tôi vào năm 2013. Tôi đã viết bài luận này và chia sẻ nó trên nền tảng này một thời gian ngắn vào năm 2013.

Language