Ưu / nhược điểm của việc sử dụng redux-saga với trình tạo ES6 so với redux-thunk với ES2017 async / await

510
hampusohlsson 2016-01-22 07:45.

Hiện có rất nhiều lời bàn tán về đứa trẻ mới nhất trong thị trấn redux , redux-saga / redux-saga . Nó sử dụng các chức năng của máy phát điện để nghe / gửi các hành động.

Trước khi tôi nghĩ về nó, tôi muốn biết ưu / nhược điểm của việc sử dụng redux-sagathay vì cách tiếp cận bên dưới mà tôi đang sử dụng redux-thunkvới async / await.

Một thành phần có thể trông như thế này, gửi các hành động như bình thường.

import { login } from 'redux/auth';

class LoginForm extends Component {

  onClick(e) {
    e.preventDefault();
    const { user, pass } = this.refs;
    this.props.dispatch(login(user.value, pass.value));
  }

  render() {
    return (<div>
        <input type="text" ref="user" />
        <input type="password" ref="pass" />
        <button onClick={::this.onClick}>Sign In</button>
    </div>);
  } 
}

export default connect((state) => ({}))(LoginForm);

Sau đó, hành động của tôi trông giống như sau:

// auth.js

import request from 'axios';
import { loadUserData } from './user';

// define constants
// define initial state
// export default reducer

export const login = (user, pass) => async (dispatch) => {
    try {
        dispatch({ type: LOGIN_REQUEST });
        let { data } = await request.post('/login', { user, pass });
        await dispatch(loadUserData(data.uid));
        dispatch({ type: LOGIN_SUCCESS, data });
    } catch(error) {
        dispatch({ type: LOGIN_ERROR, error });
    }
}

// more actions...

// user.js

import request from 'axios';

// define constants
// define initial state
// export default reducer

export const loadUserData = (uid) => async (dispatch) => {
    try {
        dispatch({ type: USERDATA_REQUEST });
        let { data } = await request.get(`/users/${uid}`);
        dispatch({ type: USERDATA_SUCCESS, data });
    } catch(error) {
        dispatch({ type: USERDATA_ERROR, error });
    }
}

// more actions...

9 answers

472
Yassine Elouafi 2016-01-22 10:12.

Trong redux-saga, tương đương với ví dụ trên sẽ là

export function* loginSaga() {
  while(true) {
    const { user, pass } = yield take(LOGIN_REQUEST)
    try {
      let { data } = yield call(request.post, '/login', { user, pass });
      yield fork(loadUserData, data.uid);
      yield put({ type: LOGIN_SUCCESS, data });
    } catch(error) {
      yield put({ type: LOGIN_ERROR, error });
    }  
  }
}

export function* loadUserData(uid) {
  try {
    yield put({ type: USERDATA_REQUEST });
    let { data } = yield call(request.get, `/users/${uid}`);
    yield put({ type: USERDATA_SUCCESS, data });
  } catch(error) {
    yield put({ type: USERDATA_ERROR, error });
  }
}

Điều đầu tiên cần chú ý là chúng ta đang gọi các hàm api bằng biểu mẫu yield call(func, ...args). callkhông thực thi hiệu ứng, nó chỉ tạo ra một đối tượng đơn giản như {type: 'CALL', func, args}. Việc thực thi được ủy quyền cho phần mềm trung gian redux-saga, phần mềm này sẽ đảm nhận việc thực thi chức năng và tiếp tục trình tạo với kết quả của nó.

Ưu điểm chính là bạn có thể kiểm tra trình tạo bên ngoài Redux bằng cách sử dụng các kiểm tra bình đẳng đơn giản

const iterator = loginSaga()

assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))

// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
  iterator.next(mockAction).value, 
  call(request.post, '/login', mockAction)
)

// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
  iterator.throw(mockError).value, 
  put({ type: LOGIN_ERROR, error: mockError })
)

Lưu ý rằng chúng tôi đang chế nhạo kết quả cuộc gọi api bằng cách chỉ cần đưa dữ liệu được mô phỏng vào nextphương thức của trình lặp. Chế độ dữ liệu đơn giản hơn so với các chức năng chế nhạo.

Điều thứ hai cần chú ý là cuộc gọi đến yield take(ACTION). Thunks được gọi bởi người tạo hành động trên mỗi hành động mới (ví dụ LOGIN_REQUEST:). tức là các hành động liên tục bị đẩy thành côn đồ, và côn đồ không kiểm soát được thời điểm dừng xử lý các hành động đó.

Trong redux-saga, máy phát điện kéo hành động tiếp theo. tức là họ có quyền kiểm soát khi nào nên lắng nghe một số hành động, và khi nào thì không. Trong ví dụ trên, các hướng dẫn luồng được đặt bên trong một while(true)vòng lặp, vì vậy nó sẽ lắng nghe từng hành động đến, điều này phần nào bắt chước hành vi đẩy mạnh.

Cách tiếp cận kéo cho phép thực hiện các luồng điều khiển phức tạp. Ví dụ, giả sử chúng ta muốn thêm các yêu cầu sau

  • Xử lý hành động của người dùng LOGOUT

  • sau lần đăng nhập thành công đầu tiên, máy chủ trả về mã thông báo hết hạn sau một thời gian trễ được lưu trữ trong một expires_intrường. Chúng tôi sẽ phải làm mới ủy quyền trong nền trên mỗi expires_inmili giây

  • Lưu ý rằng khi chờ đợi kết quả của lệnh gọi api (đăng nhập lần đầu hoặc làm mới), người dùng có thể đăng xuất ở giữa.

Làm thế nào bạn sẽ thực hiện điều đó với côn đồ; đồng thời cung cấp phạm vi kiểm tra đầy đủ cho toàn bộ luồng? Đây là cách nó có thể trông với Sagas:

function* authorize(credentials) {
  const token = yield call(api.authorize, credentials)
  yield put( login.success(token) )
  return token
}

function* authAndRefreshTokenOnExpiry(name, password) {
  let token = yield call(authorize, {name, password})
  while(true) {
    yield call(delay, token.expires_in)
    token = yield call(authorize, {token})
  }
}

function* watchAuth() {
  while(true) {
    try {
      const {name, password} = yield take(LOGIN_REQUEST)

      yield race([
        take(LOGOUT),
        call(authAndRefreshTokenOnExpiry, name, password)
      ])

      // user logged out, next while iteration will wait for the
      // next LOGIN_REQUEST action

    } catch(error) {
      yield put( login.error(error) )
    }
  }
}

Trong ví dụ trên, chúng tôi đang thể hiện yêu cầu đồng thời của mình bằng cách sử dụng race. Nếu take(LOGOUT)thắng cuộc đua (tức là người dùng đã nhấp vào nút Đăng xuất). Cuộc đua sẽ tự động hủy bỏ authAndRefreshTokenOnExpirynhiệm vụ nền. Và nếu cuộc gọi authAndRefreshTokenOnExpirybị chặn giữa chừng call(authorize, {token})thì nó cũng sẽ bị hủy. Quá trình hủy tự động truyền xuống.

Bạn có thể tìm thấy bản demo có thể chạy được của quy trình trên

108
yjcxy12 2016-06-10 21:41.

Tôi sẽ bổ sung thêm kinh nghiệm sử dụng saga trong hệ thống sản xuất ngoài câu trả lời khá kỹ lưỡng của tác giả thư viện.

Pro (sử dụng saga):

  • Khả năng kiểm tra. Rất dễ dàng để kiểm tra sagas khi call () trả về một đối tượng thuần túy. Kiểm tra côn thường yêu cầu bạn bao gồm một MockStore bên trong bài kiểm tra của bạn.

  • redux-saga đi kèm với rất nhiều chức năng trợ giúp hữu ích về các tác vụ. Đối với tôi, có vẻ như khái niệm về saga là tạo ra một số loại công nhân / luồng nền cho ứng dụng của bạn, hoạt động như một phần còn thiếu trong kiến ​​trúc redux phản ứng (actionCreators và Reducers phải là các chức năng thuần túy). Điều này dẫn đến điểm tiếp theo.

  • Sagas cung cấp một nơi độc lập để xử lý tất cả các tác dụng phụ. Theo kinh nghiệm của tôi, việc sửa đổi và quản lý thường dễ dàng hơn các hành động côn đồ.

Con:

  • Cú pháp trình tạo.

  • Rất nhiều khái niệm để học.

  • Tính ổn định của API. Có vẻ như redux-saga vẫn đang bổ sung thêm các tính năng (ví dụ: Kênh?) Và cộng đồng chưa lớn. Có một mối lo ngại nếu một ngày nào đó thư viện thực hiện một bản cập nhật không tương thích ngược.

34
madox2 2017-10-13 12:06.

Tôi chỉ muốn thêm một số nhận xét từ kinh nghiệm cá nhân của tôi (sử dụng cả sagas và thunk):

Sagas rất tuyệt để thử nghiệm:

  • Bạn không cần phải mô phỏng các chức năng được bao bọc bởi các hiệu ứng
  • Do đó các bài kiểm tra rõ ràng, dễ đọc và dễ viết
  • Khi sử dụng sagas, người tạo hành động chủ yếu trả về các ký tự đối tượng đơn giản. Nó cũng dễ dàng hơn để kiểm tra và khẳng định không giống như những lời hứa của thunk.

Sagas mạnh mẽ hơn. Tất cả những gì bạn có thể làm trong một tác phẩm hành động của một kẻ thù, bạn cũng có thể làm trong một câu chuyện, nhưng không phải ngược lại (hoặc ít nhất là không dễ dàng). Ví dụ:

  • đợi một hành động / hành động được gửi đi ( take)
  • hủy thường xuyên hiện có ( cancel, takeLatest, race)
  • nhiều thói quen có thể lắng nghe những hành động tương tự ( take, takeEvery, ...)

Sagas cũng cung cấp các chức năng hữu ích khác, khái quát một số mẫu ứng dụng phổ biến:

  • channels để nghe trên các nguồn sự kiện bên ngoài (ví dụ: websockets)
  • mô hình ngã ba ( fork, spawn)
  • ga
  • ...

Sagas là công cụ tuyệt vời và mạnh mẽ. Tuy nhiên với quyền lực đi kèm với trách nhiệm. Khi ứng dụng của bạn phát triển, bạn có thể dễ dàng bị lạc bằng cách tìm ra ai đang chờ hành động được gửi đi hoặc mọi thứ sẽ xảy ra khi một hành động nào đó được thực hiện. Mặt khác, thunk đơn giản và dễ lập luận hơn. Lựa chọn cái này hay cái khác phụ thuộc vào nhiều khía cạnh như loại và quy mô của dự án, loại tác dụng phụ nào mà dự án của bạn phải xử lý hoặc sở thích của nhóm dev. Trong mọi trường hợp, chỉ cần giữ cho ứng dụng của bạn đơn giản và dễ đoán.

16
Jonathan 2019-03-28 03:26.

Cập nhật vào tháng 7 năm 2020:

Trong 16 tháng qua, có lẽ sự thay đổi đáng chú ý nhất trong cộng đồng ReactReact hooks .

Theo những gì tôi quan sát, để có được khả năng tương thích tốt hơn với các thành phần và móc chức năng, các dự án (thậm chí cả những dự án lớn đó) sẽ có xu hướng sử dụng:

  1. hook + async thunk (hook làm cho mọi thứ rất linh hoạt để bạn thực sự có thể đặt async thunk ở nơi bạn muốn và sử dụng nó như các hàm bình thường, ví dụ: vẫn viết thunk trong action.ts và sau đó sử dụngDispatch () để kích hoạt thunk: https: //stackoverflow.com/a/59991104/5256695 ),
  2. useRequest ,
  3. GraphQL / Apollo useQuery useMutation
  4. thư viện phản ứng tìm nạp
  5. các lựa chọn phổ biến khác về tìm nạp dữ liệu / thư viện lệnh gọi API, công cụ, mẫu thiết kế, v.v.

So sánh, redux-sagakhông thực sự mang lại lợi ích đáng kể trong hầu hết các trường hợp thông thường của lệnh gọi API so với các cách tiếp cận ở trên hiện tại, đồng thời làm tăng độ phức tạp của dự án bằng cách giới thiệu nhiều tệp saga / trình tạo (cũng vì bản phát hành cuối cùng v1.1.1 của redux-sagalà vào ngày 18 tháng 9 2019, đã lâu lắm rồi).

Nhưng vẫn redux-sagacung cấp một số tính năng độc đáo như hiệu ứng đua xe và các yêu cầu song song. Vì vậy, nếu bạn cần những chức năng đặc biệt, redux-sagavẫn là một lựa chọn tốt.


Bài gốc vào tháng 3 năm 2019:

Chỉ là một số kinh nghiệm cá nhân:

  1. Đối với phong cách mã hóa và khả năng đọc, một trong những lợi thế đáng kể nhất của việc sử dụng redux-saga trong quá khứ là tránh được địa ngục gọi lại trong redux-thunk - người ta không cần phải sử dụng nhiều lồng then / catch nữa. Nhưng bây giờ với sự phổ biến của async / await thunk, người ta cũng có thể viết mã async theo kiểu đồng bộ khi sử dụng redux-thunk, đây có thể được coi là một cải tiến trong redux-thunk.

  2. Người ta có thể cần viết nhiều mã soạn sẵn hơn khi sử dụng redux-saga, đặc biệt là trong Typecript. Ví dụ: nếu một người muốn triển khai một hàm không đồng bộ tìm nạp, thì việc xử lý dữ liệu và lỗi có thể được thực hiện trực tiếp trong một đơn vị thunk trong action.js với một hành động FETCH duy nhất. Nhưng trong redux-saga, người ta có thể cần xác định các hành động FETCH_START, FETCH_SUCCESS và FETCH_FAILURE và tất cả các kiểu kiểm tra liên quan của chúng, bởi vì một trong những tính năng trong redux-saga là sử dụng loại cơ chế “mã thông báo” phong phú này để tạo hiệu ứng và hướng dẫn cửa hàng redux để dễ dàng kiểm tra. Tất nhiên người ta có thể viết một câu chuyện mà không cần sử dụng những thao tác này, nhưng điều đó sẽ khiến nó tương tự như một cú đánh.

  3. Về cấu trúc tệp, redux-saga có vẻ rõ ràng hơn trong nhiều trường hợp. Người ta có thể dễ dàng tìm thấy mã liên quan đến async trong mọi sagas.ts, nhưng trong redux-thunk, người ta sẽ cần phải xem nó trong các hành động.

  4. Kiểm tra dễ dàng có thể là một tính năng có trọng số khác trong redux-saga. Điều này thực sự thuận tiện. Nhưng một điều cần được làm rõ là kiểm tra "cuộc gọi" redux-saga sẽ không thực hiện lệnh gọi API thực tế trong quá trình thử nghiệm, do đó người ta sẽ cần chỉ định kết quả mẫu cho các bước có thể được sử dụng sau lệnh gọi API. Do đó, trước khi viết bằng redux-saga, tốt hơn là bạn nên lập kế hoạch chi tiết về một saga và các sagas.spec.ts tương ứng của nó.

  5. Redux-saga cũng cung cấp nhiều tính năng nâng cao như chạy các tác vụ song song, các trình trợ giúp đồng thời như takeLatest / takeEvery, fork / spawn, mạnh hơn rất nhiều so với thunks.

Về mặt cá nhân, tôi muốn nói rằng: trong nhiều trường hợp bình thường và các ứng dụng có kích thước vừa và nhỏ, hãy sử dụng kiểu redux-thunk kiểu async / await. Nó sẽ giúp bạn tiết kiệm nhiều mã soạn sẵn / hành động / typedefs và bạn sẽ không cần phải chuyển đổi nhiều sagas.ts khác nhau và duy trì một cây sagas cụ thể. Nhưng nếu bạn đang phát triển một ứng dụng lớn với nhiều logic không đồng bộ phức tạp và cần các tính năng như mẫu đồng thời / song song hoặc có nhu cầu cao về kiểm tra và bảo trì (đặc biệt là trong phát triển theo hướng thử nghiệm), redux-sagas có thể sẽ cứu mạng bạn .

Dù sao, redux-saga không khó và phức tạp hơn redux, và nó không có cái gọi là đường cong học tập dốc vì nó có các khái niệm và API cốt lõi rất hạn chế. Dành một ít thời gian để học redux-saga có thể mang lại lợi ích cho chính bạn vào một ngày nào đó trong tương lai.

5
David Bradshaw 2018-06-15 11:04.

Theo kinh nghiệm của tôi, sau khi xem xét một số dự án React / Redux quy mô lớn khác nhau, Sagas cung cấp cho các nhà phát triển một cách viết mã có cấu trúc hơn, dễ kiểm tra hơn và khó sai hơn.

Vâng, nó là một chút khó khăn để bắt đầu, nhưng hầu hết các nhà phát triển có đủ hiểu biết về nó trong một ngày. Tôi luôn nói với mọi người rằng đừng lo lắng về những gì yieldphải bắt đầu và rằng một khi bạn viết một vài bài kiểm tra, nó sẽ đến với bạn.

Tôi đã thấy một vài dự án mà những kẻ côn đồ được đối xử như thể chúng là người điều khiển từ MVC vỗ về và điều này nhanh chóng trở thành một mớ hỗn độn không thể xóa nhòa.

Lời khuyên của tôi là sử dụng Sagas khi bạn cần loại A kích hoạt nội dung loại B liên quan đến một sự kiện duy nhất. Đối với bất kỳ thứ gì có thể cắt ngang một số hành động, tôi thấy đơn giản hơn là viết phần mềm trung gian của khách hàng và sử dụng thuộc tính meta của một hành động FSA để kích hoạt nó.

2
Mselmi Ali 2019-08-01 05:11.

Thunks đấu với Sagas

Redux-ThunkRedux-Sagakhác nhau ở một vài điểm quan trọng, cả hai đều là thư viện phần mềm trung gian cho Redux (Phần mềm trung gian Redux là mã chặn các hành động đi vào cửa hàng thông qua phương thức Dispatch ()).

Một hành động có thể là bất cứ điều gì theo nghĩa đen, nhưng nếu bạn đang làm theo các phương pháp hay nhất, thì một hành động là một đối tượng javascript thuần với trường loại và các trường trọng tải, meta và lỗi tùy chọn. ví dụ

const loginRequest = {
    type: 'LOGIN_REQUEST',
    payload: {
        name: 'admin',
        password: '123',
    }, };

Redux-Thunk

Ngoài việc gửi các hành động tiêu chuẩn, Redux-Thunkphần mềm trung gian cho phép bạn gửi các chức năng đặc biệt, được gọi là thunks.

Thunks (trong Redux) thường có cấu trúc sau:

export const thunkName =
   parameters =>
        (dispatch, getState) => {
            // Your application logic goes here
        };

Tức là, a thunklà một hàm (tùy chọn) nhận một số tham số và trả về một hàm khác. Hàm bên trong nhận một dispatch functionvà một getStatehàm - cả hai sẽ được cung cấp bởi Redux-Thunkphần mềm trung gian.

Redux-Saga

Redux-Sagaphần mềm trung gian cho phép bạn thể hiện logic ứng dụng phức tạp dưới dạng các hàm thuần túy được gọi là sagas. Các chức năng thuần túy được mong muốn từ quan điểm kiểm tra vì chúng có thể dự đoán và lặp lại, điều này làm cho chúng tương đối dễ kiểm tra.

Sagas được thực hiện thông qua các chức năng đặc biệt được gọi là chức năng máy phát điện. Đây là một tính năng mới của ES6 JavaScript. Về cơ bản, quá trình thực thi sẽ nhảy vào và ra khỏi trình tạo ở mọi nơi bạn nhìn thấy báo cáo lợi nhuận. Hãy nghĩ về một yieldcâu lệnh làm cho trình tạo tạm dừng và trả về giá trị đã thu được. Sau đó, người gọi có thể tiếp tục trình tạo tại câu lệnh sau yield.

Hàm máy phát điện là một hàm được định nghĩa như thế này. Chú ý dấu sao sau từ khóa hàm.

function* mySaga() {
    // ...
}

Khi câu chuyện đăng nhập được đăng ký với Redux-Saga. Nhưng sau đó, yielddòng đầu tiên sẽ tạm dừng câu chuyện cho đến khi một hành động với loại 'LOGIN_REQUEST'được gửi đến cửa hàng. Khi điều đó xảy ra, việc thực thi sẽ tiếp tục.

Để biết thêm chi tiết xem bài viết này .

1
Dmitriy 2018-06-15 12:11.

Một lưu ý nhanh chóng. Máy phát điện có thể hủy, không đồng bộ / chờ - không. Vì vậy, đối với một ví dụ từ câu hỏi, nó không thực sự có ý nghĩa về những gì để chọn. Nhưng đối với những dòng chảy phức tạp hơn đôi khi không có giải pháp nào tốt hơn là sử dụng máy phát điện.

Vì vậy, một ý tưởng khác có thể là sử dụng máy phát điện với redux-thunk, nhưng đối với tôi, nó giống như đang cố gắng phát minh ra một chiếc xe đạp có bánh hình vuông.

Và tất nhiên, máy phát điện dễ kiểm tra hơn.

0
Diego Haz 2017-05-23 17:39.

Đây là một dự án kết hợp các phần tốt nhất (ưu) của cả hai redux-sagaredux-thunk: bạn có thể xử lý tất cả các tác dụng phụ trên sagas trong khi nhận được lời hứa bằng dispatchinghành động tương ứng: https://github.com/diegohaz/redux-saga-thunk

class MyComponent extends React.Component {
  componentWillMount() {
    // `doSomething` dispatches an action which is handled by some saga
    this.props.doSomething().then((detail) => {
      console.log('Yaay!', detail)
    }).catch((error) => {
      console.log('Oops!', error)
    })
  }
}
0
codemeasandwich 2017-06-25 03:25.

Một cách dễ dàng hơn là sử dụng redux-auto .

từ documantasion

redux-auto đã khắc phục sự cố không đồng bộ này chỉ bằng cách cho phép bạn tạo một hàm "hành động" trả về một lời hứa. Để đi kèm với logic hành động chức năng "mặc định" của bạn.

  1. Không cần phần mềm trung gian không đồng bộ Redux khác. ví dụ: thunk, Promise-middleware, saga
  2. Dễ dàng cho phép bạn chuyển một lời hứa vào redux và quản lý nó cho bạn
  3. Cho phép bạn đồng định vị các cuộc gọi dịch vụ bên ngoài với nơi chúng sẽ được chuyển đổi
  4. Đặt tên tệp là "init.js" sẽ gọi nó một lần khi khởi động ứng dụng. Điều này rất tốt cho việc tải dữ liệu từ máy chủ khi bắt đầu

Ý tưởng là có mỗi hành động trong một tệp cụ thể . đồng định vị lệnh gọi máy chủ trong tệp với các hàm giảm thiểu cho "đang chờ xử lý", "hoàn thành" và "bị từ chối". Điều này làm cho việc xử lý các lời hứa rất dễ dàng.

Nó cũng tự động đính kèm một đối tượng trợ giúp (được gọi là "async") vào nguyên mẫu trạng thái của bạn, cho phép bạn theo dõi trong giao diện người dùng của mình, các chuyển đổi được yêu cầu.

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.

Làm thế nào để xây dựng một quả địa cầu từ Scratch

Làm thế nào để xây dựng một quả địa cầu từ Scratch

Thời gian, sự kiên nhẫn, thời gian, sự cống hiến và thời gian chỉ là một vài trong số những thứ mà Peter Bellerby cần để thành lập và sau đó điều hành Bellerby & Co. Globemakers, một trong những công ty duy nhất trên Trái đất vẫn sản xuất các quả địa cầu bằng tay.

Năm giai đoạn đau buồn sau khi mất việc làm

Năm giai đoạn đau buồn sau khi mất việc làm

Đó là một ngày thứ Bảy, máy bay của tôi hạ cánh, và tôi đã sẵn sàng để thư giãn trong một kỳ nghỉ cuối tuần ngắn ngủi, khi một email đến trên điện thoại của tôi. Tôi đã mất việc.

Giữ an toàn cho danh tính của bạn với một cảnh báo đóng băng hoặc gian lận tín dụng

Giữ an toàn cho danh tính của bạn với một cảnh báo đóng băng hoặc gian lận tín dụng

Nếu bạn đã từng bị đánh cắp danh tính của mình, bạn biết đó là một trải nghiệm đáng sợ và căng thẳng. Một cách không phổ biến để ngăn chặn nó? Tín dụng bị đóng băng.

Buổi ra mắt phần 3 của The Good Place có một học sinh mới: Khán giả

Buổi ra mắt phần 3 của The Good Place có một học sinh mới: Khán giả

Eleanor (Kristen Bell) dường như đã tình nguyện cho chúng tôi làm vật tưởng nhớ. NBC's The Good Place là một chương trình thích thử thách tốt.

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

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Vụ kiện, nêu tên một số học khu, lập luận rằng dự luật "Không nói đồng tính" được ban hành gần đây của Florida "có hiệu quả im lặng và xóa bỏ học sinh và gia đình LGBTQ +"

Đườ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.

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Tôi ghét từ "tàu đắm". Mọi người cảm thấy thoải mái trong la bàn đạo đức của riêng mình, và khi làm như vậy, họ thấy mình vượt qua sự phán xét.

Language