Làm thế nào để gửi một hành động Redux với thời gian chờ?

930
Ilja 2016-02-16 04:03.

Tôi có một hành động cập nhật trạng thái thông báo của ứng dụng của mình. Thông thường, thông báo này sẽ là một lỗi hoặc một số loại thông tin. Sau đó, tôi cần thực hiện một hành động khác sau 5 giây sẽ đưa trạng thái thông báo trở lại trạng thái ban đầu, vì vậy không có thông báo nào. Lý do chính đằng sau điều này là cung cấp chức năng nơi thông báo tự động biến mất sau 5 giây.

Tôi đã không gặp may khi sử dụng setTimeoutvà trả lại một hành động khác và không thể tìm thấy cách này được thực hiện trực tuyến. Vì vậy, mọi lời khuyên đều được hoan nghênh.

12 answers

2709
Dan Abramov 2016-02-16 07:33.

Đừng rơi vào bẫy khi nghĩ rằng một thư viện nên quy định cách làm mọi thứ . Nếu bạn muốn làm điều gì đó với thời gian chờ trong JavaScript, bạn cần sử dụng setTimeout. Không có lý do gì tại sao các hành động của Redux lại khác.

Redux không cung cấp một số cách khác nhau để đối phó với những thứ không đồng bộ, nhưng bạn chỉ nên sử dụng những khi bạn nhận ra bạn đang lặp đi lặp lại quá nhiều mã. Trừ khi bạn gặp vấn đề này, hãy sử dụng những gì ngôn ngữ cung cấp và tìm giải pháp đơn giản nhất.

Viết nội tuyến mã không đồng bộ

Đây là cách đơn giản nhất. Và không có gì cụ thể cho Redux ở đây.

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

Tương tự, từ bên trong một thành phần được kết nối:

this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

Sự khác biệt duy nhất là trong một thành phần được kết nối, bạn thường không có quyền truy cập vào chính cửa hàng, nhưng được đưa vào một trong hai dispatch()hoặc người tạo hành động cụ thể làm đạo cụ. Tuy nhiên, điều này không tạo ra sự khác biệt nào đối với chúng tôi.

Nếu bạn không thích mắc lỗi chính tả khi gửi các hành động giống nhau từ các thành phần khác nhau, bạn có thể muốn trích xuất trình tạo hành động thay vì gửi nội tuyến đối tượng hành động:

// actions.js
export function showNotification(text) {
  return { type: 'SHOW_NOTIFICATION', text }
}
export function hideNotification() {
  return { type: 'HIDE_NOTIFICATION' }
}

// component.js
import { showNotification, hideNotification } from '../actions'

this.props.dispatch(showNotification('You just logged in.'))
setTimeout(() => {
  this.props.dispatch(hideNotification())
}, 5000)

Hoặc, nếu trước đây bạn đã ràng buộc họ với connect():

this.props.showNotification('You just logged in.')
setTimeout(() => {
  this.props.hideNotification()
}, 5000)

Cho đến nay chúng tôi chưa sử dụng bất kỳ phần mềm trung gian hoặc khái niệm nâng cao nào khác.

Giải nén Trình tạo hành động không đồng bộ

Phương pháp trên hoạt động tốt trong các trường hợp đơn giản nhưng bạn có thể thấy rằng nó có một số vấn đề:

  • Nó buộc bạn phải sao chép logic này ở bất kỳ đâu bạn muốn hiển thị thông báo.
  • Các thông báo không có ID nên bạn sẽ có điều kiện chạy đua nếu bạn hiển thị hai thông báo đủ nhanh. Khi hết thời gian chờ đầu tiên, nó sẽ gửi đi HIDE_NOTIFICATION, ẩn thông báo thứ hai một cách sai lầm sớm hơn sau thời gian chờ.

Để giải quyết những vấn đề này, bạn sẽ cần trích xuất một hàm tập trung logic thời gian chờ và thực hiện hai hành động đó. Nó có thể trông như thế này:

// actions.js
function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
  // Assigning IDs to notifications lets reducer ignore HIDE_NOTIFICATION
  // for the notification that is not currently visible.
  // Alternatively, we could store the timeout ID and call
  // clearTimeout(), but we’d still want to do it in a single place.
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

Giờ đây, các thành phần có thể sử dụng showNotificationWithTimeoutmà không cần sao chép logic này hoặc có các điều kiện chạy đua với các thông báo khác nhau:

// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')

// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')    

Tại sao lại showNotificationWithTimeout()chấp nhận dispatchlà đối số đầu tiên? Bởi vì nó cần gửi các hành động đến cửa hàng. Thông thường một thành phần có quyền truy cập vào dispatchnhưng vì chúng ta muốn một chức năng bên ngoài kiểm soát việc điều phối, chúng ta cần cấp cho nó quyền kiểm soát việc điều phối.

Nếu bạn đã xuất một cửa hàng singleton từ một số mô-đun, bạn chỉ có thể nhập nó và dispatchtrực tiếp trên đó:

// store.js
export default createStore(reducer)

// actions.js
import store from './store'

// ...

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  const id = nextNotificationId++
  store.dispatch(showNotification(id, text))

  setTimeout(() => {
    store.dispatch(hideNotification(id))
  }, 5000)
}

// component.js
showNotificationWithTimeout('You just logged in.')

// otherComponent.js
showNotificationWithTimeout('You just logged out.')    

Cách này trông đơn giản hơn nhưng chúng tôi không khuyên bạn nên làm theo cách này . Lý do chính mà chúng tôi không thích nó là vì nó buộc cửa hàng phải là một singleton . Điều này làm cho nó rất khó thực hiện kết xuất máy chủ . Trên máy chủ, bạn sẽ muốn mỗi yêu cầu có một cửa hàng riêng để những người dùng khác nhau có được dữ liệu tải trước khác nhau.

Một cửa hàng singleton cũng làm cho việc kiểm tra khó hơn. Bạn không còn có thể chế nhạo một cửa hàng khi thử nghiệm người tạo hành động vì họ tham chiếu đến một cửa hàng thực cụ thể được xuất từ ​​một mô-đun cụ thể. Bạn thậm chí không thể thiết lập lại trạng thái của nó từ bên ngoài.

Vì vậy, trong khi về mặt kỹ thuật, bạn có thể xuất một kho lưu trữ singleton từ một mô-đun, chúng tôi không khuyến khích điều đó. Đừng làm điều này trừ khi bạn chắc chắn rằng ứng dụng của mình sẽ không bao giờ thêm kết xuất máy chủ.

Quay lại phiên bản trước:

// actions.js

// ...

let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')

// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')    

Điều này giải quyết các vấn đề về sự trùng lặp logic và giúp chúng ta thoát khỏi các điều kiện chủng tộc.

Thunk Middleware

Đối với các ứng dụng đơn giản, phương pháp này là đủ. Đừng lo lắng về phần mềm trung gian nếu bạn hài lòng với nó.

Tuy nhiên, trong các ứng dụng lớn hơn, bạn có thể thấy những bất tiện nhất định xung quanh nó.

Ví dụ, có vẻ như không may là chúng ta phải vượt qua dispatch. Điều này làm cho việc tách các thành phần vùng chứa và trình bày phức tạp hơn vì bất kỳ thành phần nào gửi các hành động Redux không đồng bộ theo cách trên đều phải chấp nhận dispatchnhư một chỗ dựa để nó có thể vượt qua nó. Bạn không thể ràng buộc người sáng tạo hành động connect()nữa vì showNotificationWithTimeout()không thực sự là người tạo hành động. Nó không trả về một hành động Redux.

Ngoài ra, có thể khó nhớ khi nhớ chức năng nào mà người tạo hành động đồng bộ thích showNotification()và chức năng nào là người trợ giúp không đồng bộ showNotificationWithTimeout(). Bạn phải sử dụng chúng khác nhau và cẩn thận để không nhầm chúng với nhau.

Đây là động lực để tìm cách “hợp pháp hóa” mô hình cung cấp dispatchchức năng trợ giúp này và giúp Redux “xem” những người tạo hành động không đồng bộ như một trường hợp đặc biệt của những người tạo hành động bình thường thay vì các chức năng hoàn toàn khác.

Nếu bạn vẫn ở với chúng tôi và bạn cũng nhận ra sự cố trong ứng dụng của mình, bạn có thể sử dụng phần mềm trung gian Redux Thunk .

Tóm lại, Redux Thunk dạy Redux nhận ra các loại hành động đặc biệt mà trên thực tế là các chức năng:

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

const store = createStore(
  reducer,
  applyMiddleware(thunk)
)

// It still recognizes plain object actions
store.dispatch({ type: 'INCREMENT' })

// But with thunk middleware, it also recognizes functions
store.dispatch(function (dispatch) {
  // ... which themselves may dispatch many times
  dispatch({ type: 'INCREMENT' })
  dispatch({ type: 'INCREMENT' })
  dispatch({ type: 'INCREMENT' })

  setTimeout(() => {
    // ... even asynchronously!
    dispatch({ type: 'DECREMENT' })
  }, 1000)
})

Khi phần mềm trung gian này được kích hoạt, nếu bạn gửi một chức năng , phần mềm trung gian Redux Thunk sẽ cung cấp cho nó dispatchnhư một đối số. Nó cũng sẽ "nuốt" các hành động như vậy nên đừng lo lắng về việc bộ giảm của bạn nhận được các đối số hàm kỳ lạ. Bộ giảm tốc của bạn sẽ chỉ nhận được các hành động đối tượng thuần túy — được phát trực tiếp hoặc được phát ra bởi các hàm như chúng tôi vừa mô tả.

Điều này trông không hữu ích cho lắm, phải không? Không phải trong tình huống cụ thể này. Tuy nhiên, nó cho phép chúng tôi khai báo showNotificationWithTimeout()như một người tạo hành động Redux thông thường:

// actions.js
function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch) {
    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

Lưu ý cách hàm gần như giống với hàm mà chúng ta đã viết trong phần trước. Tuy nhiên nó không chấp nhận dispatchlà đối số đầu tiên. Thay vào đó, nó trả về một hàm chấp nhận dispatchlàm đối số đầu tiên.

Làm thế nào chúng tôi sẽ sử dụng nó trong thành phần của chúng tôi? Chắc chắn, chúng tôi có thể viết điều này:

// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)

Chúng tôi đang gọi trình tạo hành động không đồng bộ để có được chức năng bên trong theo ý muốn dispatchvà sau đó chúng tôi vượt qua dispatch.

Tuy nhiên điều này thậm chí còn khó xử hơn so với phiên bản gốc! Tại sao chúng ta lại đi theo cách đó?

Vì những gì tôi đã nói với bạn trước đây. Nếu phần mềm trung gian Redux Thunk được bật, bất cứ khi nào bạn cố gắng gửi một hàm thay vì một đối tượng hành động, phần mềm trung gian sẽ gọi hàm đó với dispatchchính phương thức làm đối số đầu tiên .

Vì vậy, chúng tôi có thể làm điều này thay thế:

// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))

Cuối cùng, việc gửi một hành động không đồng bộ (thực sự là một chuỗi các hành động) trông không khác gì việc điều phối một hành động đơn lẻ một cách đồng bộ tới thành phần. Điều này là tốt vì các thành phần không nên quan tâm đến việc điều gì đó xảy ra đồng bộ hay không đồng bộ. Chúng tôi chỉ tóm tắt điều đó đi.

Chú ý rằng kể từ khi chúng tôi “dạy” Redux nhận như người sáng tạo hành động “đặc biệt” (chúng tôi gọi họ thunk sáng tạo hành động), chúng tôi bây giờ có thể sử dụng chúng ở bất cứ nơi mà chúng tôi sẽ sử dụng sáng tạo hành động thông thường. Ví dụ: chúng ta có thể sử dụng chúng với connect():

// actions.js

function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch) {
    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

// component.js

import { connect } from 'react-redux'

// ...

this.props.showNotificationWithTimeout('You just logged in.')

// ...

export default connect(
  mapStateToProps,
  { showNotificationWithTimeout }
)(MyComponent)

Trạng thái đọc trong Thunks

Thông thường các bộ giảm của bạn chứa logic nghiệp vụ để xác định trạng thái tiếp theo. Tuy nhiên, bộ giảm chỉ hoạt động sau khi các hành động được thực hiện. Điều gì sẽ xảy ra nếu bạn gặp phải tác dụng phụ (chẳng hạn như gọi một API) trong trình tạo hành động va chạm và bạn muốn ngăn chặn nó trong một số điều kiện?

Nếu không sử dụng phần mềm trung gian thunk, bạn chỉ cần thực hiện kiểm tra bên trong thành phần:

// component.js
if (this.props.areNotificationsEnabled) {
  showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}

Tuy nhiên, điểm mấu chốt của việc trích xuất một trình tạo hành động là tập trung logic lặp đi lặp lại này trên nhiều thành phần. May mắn thay, Redux Thunk cung cấp cho bạn một cách để đọc trạng thái hiện tại của cửa hàng Redux. Ngoài ra dispatch, nó cũng chuyển getStatelàm đối số thứ hai cho hàm mà bạn trả về từ trình tạo hành động thunk của mình. Điều này cho phép người điều khiển đọc trạng thái hiện tại của cửa hàng.

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch, getState) {
    // Unlike in a regular action creator, we can exit early in a thunk
    // Redux doesn’t care about its return value (or lack of it)
    if (!getState().areNotificationsEnabled) {
      return
    }

    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

Đừng lạm dụng mô hình này. Nó rất tốt để loại bỏ các lệnh gọi API khi có sẵn dữ liệu được lưu trong bộ nhớ cache, nhưng nó không phải là nền tảng tốt để xây dựng logic kinh doanh của bạn. Nếu bạn getState()chỉ sử dụng để gửi các hành động khác nhau có điều kiện, hãy xem xét đưa logic nghiệp vụ vào các bộ giảm bớt.

Bước tiếp theo

Bây giờ bạn đã có trực giác cơ bản về cách thức hoạt động của côn, hãy xem ví dụ không đồng bộ của Redux sử dụng chúng.

Bạn có thể tìm thấy nhiều ví dụ trong đó côn đồ trả lại Lời hứa. Điều này không bắt buộc nhưng có thể rất thuận tiện. Redux không quan tâm những gì bạn trả về từ một cú đánh, nhưng nó cung cấp cho bạn giá trị trả về từ đó dispatch(). Đây là lý do tại sao bạn có thể trả lại Lời hứa từ một tiếng sét và đợi nó hoàn thành bằng cách gọi dispatch(someThunkReturningPromise()).then(...).

Bạn cũng có thể chia những người tạo hành động mạnh tay phức tạp thành một số người tạo hành động côn đồ nhỏ hơn. Các dispatchphương pháp được cung cấp bởi thunks thể chấp nhận thunks chính nó, vì vậy bạn có thể áp dụng mô hình một cách đệ quy. Một lần nữa, điều này hoạt động tốt nhất với Promises vì ​​bạn có thể triển khai luồng điều khiển không đồng bộ trên đó.

Đối với một số ứng dụng, bạn có thể gặp phải tình huống mà các yêu cầu về luồng điều khiển không đồng bộ của bạn quá phức tạp để được thể hiện bằng các đòn côn. Ví dụ: thử lại các yêu cầu không thành công, quy trình ủy quyền lại bằng mã thông báo hoặc giới thiệu từng bước có thể quá dài dòng và dễ xảy ra lỗi khi viết theo cách này. Trong trường hợp này, bạn có thể muốn xem xét các giải pháp luồng điều khiển không đồng bộ nâng cao hơn như Redux Saga hoặc Redux Loop . Đánh giá chúng, so sánh các ví dụ có liên quan đến nhu cầu của bạn và chọn một ví dụ bạn thích nhất.

Cuối cùng, đừng sử dụng bất cứ thứ gì (kể cả côn đồ) nếu bạn không có nhu cầu thực sự với chúng. Hãy nhớ rằng, tùy thuộc vào yêu cầu, giải pháp của bạn có thể trông đơn giản như

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

Đừng đổ mồ hôi trừ khi bạn biết tại sao bạn đang làm điều này.

195
Sebastien Lorber 2016-07-26 07:44.

Sử dụng Redux-saga

Như Dan Abramov đã nói, nếu bạn muốn kiểm soát nâng cao hơn đối với mã không đồng bộ của mình, bạn có thể xem qua redux-saga .

Câu trả lời này là một ví dụ đơn giản, nếu bạn muốn giải thích rõ hơn về lý do tại sao redux-saga có thể hữu ích cho ứng dụng của bạn, hãy kiểm tra câu trả lời khác này .

Ý tưởng chung là Redux-saga cung cấp trình thông dịch trình tạo ES6 cho phép bạn dễ dàng viết mã không đồng bộ trông giống như mã đồng bộ (đây là lý do tại sao bạn thường tìm thấy các vòng lặp trong khi vô hạn trong Redux-saga). Bằng cách nào đó, Redux-saga đang xây dựng ngôn ngữ riêng của mình ngay bên trong Javascript. Ban đầu, bạn có thể cảm thấy hơi khó học Redux-saga vì bạn cần hiểu biết cơ bản về máy phát điện, nhưng cũng phải hiểu ngôn ngữ do Redux-saga cung cấp.

Tôi sẽ cố gắng mô tả ở đây hệ thống thông báo mà tôi đã xây dựng trên redux-saga. Ví dụ này hiện đang chạy trong sản xuất.

Đặc tả hệ thống thông báo nâng cao

  • Bạn có thể yêu cầu một thông báo được hiển thị
  • Bạn có thể yêu cầu thông báo để ẩn
  • Thông báo không được hiển thị quá 4 giây
  • Nhiều thông báo có thể được hiển thị cùng một lúc
  • Không thể hiển thị nhiều hơn 3 thông báo cùng một lúc
  • Nếu một thông báo được yêu cầu trong khi đã có 3 thông báo được hiển thị, thì hãy xếp hàng / hoãn thông báo đó lại.

Kết quả

Ảnh chụp màn hình ứng dụng sản xuất của tôi Stample.co

Ở đây tôi đặt tên thông báo là a toastnhưng đây là chi tiết đặt tên.

function* toastSaga() {

    // Some config constants
    const MaxToasts = 3;
    const ToastDisplayTime = 4000;


    // Local generator state: you can put this state in Redux store
    // if it's really important to you, in my case it's not really
    let pendingToasts = []; // A queue of toasts waiting to be displayed
    let activeToasts = []; // Toasts currently displayed


    // Trigger the display of a toast for 4 seconds
    function* displayToast(toast) {
        if ( activeToasts.length >= MaxToasts ) {
            throw new Error("can't display more than " + MaxToasts + " at the same time");
        }
        activeToasts = [...activeToasts,toast]; // Add to active toasts
        yield put(events.toastDisplayed(toast)); // Display the toast (put means dispatch)
        yield call(delay,ToastDisplayTime); // Wait 4 seconds
        yield put(events.toastHidden(toast)); // Hide the toast
        activeToasts = _.without(activeToasts,toast); // Remove from active toasts
    }

    // Everytime we receive a toast display request, we put that request in the queue
    function* toastRequestsWatcher() {
        while ( true ) {
            // Take means the saga will block until TOAST_DISPLAY_REQUESTED action is dispatched
            const event = yield take(Names.TOAST_DISPLAY_REQUESTED);
            const newToast = event.data.toastData;
            pendingToasts = [...pendingToasts,newToast];
        }
    }


    // We try to read the queued toasts periodically and display a toast if it's a good time to do so...
    function* toastScheduler() {
        while ( true ) {
            const canDisplayToast = activeToasts.length < MaxToasts && pendingToasts.length > 0;
            if ( canDisplayToast ) {
                // We display the first pending toast of the queue
                const [firstToast,...remainingToasts] = pendingToasts;
                pendingToasts = remainingToasts;
                // Fork means we are creating a subprocess that will handle the display of a single toast
                yield fork(displayToast,firstToast);
                // Add little delay so that 2 concurrent toast requests aren't display at the same time
                yield call(delay,300);
            }
            else {
                yield call(delay,50);
            }
        }
    }

    // This toast saga is a composition of 2 smaller "sub-sagas" (we could also have used fork/spawn effects here, the difference is quite subtile: it depends if you want toastSaga to block)
    yield [
        call(toastRequestsWatcher),
        call(toastScheduler)
    ]
}

Và bộ giảm tốc:

const reducer = (state = [],event) => {
    switch (event.name) {
        case Names.TOAST_DISPLAYED:
            return [...state,event.data.toastData];
        case Names.TOAST_HIDDEN:
            return _.without(state,event.data.toastData);
        default:
            return state;
    }
};

Sử dụng

Bạn có thể chỉ cần gửi TOAST_DISPLAY_REQUESTEDcác sự kiện. Nếu bạn gửi 4 yêu cầu, chỉ 3 thông báo sẽ được hiển thị và yêu cầu thứ 4 sẽ xuất hiện sau một chút khi thông báo đầu tiên biến mất.

Lưu ý rằng tôi không đặc biệt khuyên bạn nên gửi TOAST_DISPLAY_REQUESTEDtừ JSX. Bạn muốn thêm một câu chuyện khác lắng nghe các sự kiện ứng dụng đã có của bạn và sau đó gửi TOAST_DISPLAY_REQUESTED: thành phần của bạn kích hoạt thông báo, không cần phải kết hợp chặt chẽ với hệ thống thông báo.

Phần kết luận

Mã của tôi không hoàn hảo nhưng chạy trong sản xuất với 0 lỗi trong nhiều tháng. Ban đầu, Redux-saga và máy phát điện hơi khó nhưng một khi bạn hiểu chúng thì hệ thống này khá dễ xây dựng.

Nó thậm chí còn khá dễ dàng để triển khai các quy tắc phức tạp hơn, như:

  • khi có quá nhiều thông báo được "xếp hàng đợi", hãy cung cấp ít thời gian hiển thị hơn cho mỗi thông báo để kích thước hàng đợi có thể giảm nhanh hơn.
  • phát hiện các thay đổi về kích thước cửa sổ và thay đổi số lượng thông báo hiển thị tối đa cho phù hợp (ví dụ: màn hình = 3, dọc điện thoại = 2, ngang điện thoại = 1)

Thành thật mà nói, chúc may mắn khi thực hiện đúng loại công cụ này với những kẻ côn đồ.

Lưu ý rằng bạn có thể làm giống hệt như vậy với redux-Observable , rất giống với redux-saga. Nó gần như giống nhau và là vấn đề thị hiếu giữa máy phát điện và RxJS.

26
Tyler Long 2016-12-25 02:53.

Kho lưu trữ với các dự án mẫu

Hiện tại có bốn dự án mẫu:

  1. Viết nội tuyến mã không đồng bộ
  2. Giải nén Trình tạo hành động không đồng bộ
  3. Sử dụng Redux Thunk
  4. Sử dụng Redux Saga

Câu trả lời được chấp nhận là tuyệt vời.

Nhưng có điều gì đó còn thiếu:

  1. Không có dự án mẫu nào có thể chạy được, chỉ một số đoạn mã.
  2. Không có mã mẫu cho các lựa chọn thay thế khác, chẳng hạn như:
    1. Redux Saga

Vì vậy, tôi đã tạo kho lưu trữ Hello Async để bổ sung những thứ còn thiếu:

  1. Các dự án có thể chạy được. Bạn có thể tải xuống và chạy chúng mà không cần sửa đổi.
  2. Cung cấp mã mẫu cho nhiều lựa chọn thay thế hơn:

Redux Saga

Câu trả lời được chấp nhận đã cung cấp các đoạn mã mẫu cho Async Code Inline, Async Action Generator và Redux Thunk. Để hoàn thiện, tôi cung cấp các đoạn mã cho Redux Saga:

// actions.js

export const showNotification = (id, text) => {
  return { type: 'SHOW_NOTIFICATION', id, text }
}

export const hideNotification = (id) => {
  return { type: 'HIDE_NOTIFICATION', id }
}

export const showNotificationWithTimeout = (text) => {
  return { type: 'SHOW_NOTIFICATION_WITH_TIMEOUT', text }
}

Các hành động rất đơn giản và thuần túy.

// component.js

import { connect } from 'react-redux'

// ...

this.props.showNotificationWithTimeout('You just logged in.')

// ...

export default connect(
  mapStateToProps,
  { showNotificationWithTimeout }
)(MyComponent)

Không có gì là đặc biệt với thành phần.

// sagas.js

import { takeEvery, delay } from 'redux-saga'
import { put } from 'redux-saga/effects'
import { showNotification, hideNotification } from './actions'

// Worker saga
let nextNotificationId = 0
function* showNotificationWithTimeout (action) {
  const id = nextNotificationId++
  yield put(showNotification(id, action.text))
  yield delay(5000)
  yield put(hideNotification(id))
}

// Watcher saga, will invoke worker saga above upon action 'SHOW_NOTIFICATION_WITH_TIMEOUT'
function* notificationSaga () {
  yield takeEvery('SHOW_NOTIFICATION_WITH_TIMEOUT', showNotificationWithTimeout)
}

export default notificationSaga

Sagas dựa trên Máy phát điện ES6

// index.js

import createSagaMiddleware from 'redux-saga'
import saga from './sagas'

const sagaMiddleware = createSagaMiddleware()

const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(saga)

So với Redux Thunk

Ưu điểm

  • Bạn không kết thúc trong địa ngục gọi lại.
  • Bạn có thể kiểm tra các luồng không đồng bộ của mình một cách dễ dàng.
  • Hành động của bạn vẫn trong sáng.

Nhược điểm

  • Nó phụ thuộc vào Máy phát điện ES6 tương đối mới.

Vui lòng tham khảo dự án có thể chạy được nếu các đoạn mã ở trên không trả lời tất cả các câu hỏi của bạn.

24
Fatih Erikli 2016-02-16 04:16.

Bạn có thể làm điều này với redux-thunk . Có một hướng dẫn trong tài liệu redux cho các hành động không đồng bộ như setTimeout.

23
Jean-Jacques Dubray 2016-02-24 16:31.

Tôi cũng khuyên bạn nên xem xét mô hình SAM .

Mẫu SAM ủng hộ việc bao gồm vị từ "hành động tiếp theo" trong đó các hành động (tự động) như "thông báo tự động biến mất sau 5 giây" được kích hoạt sau khi mô hình đã được cập nhật (mô hình SAM ~ trạng thái giảm tốc + cửa hàng).

Mẫu ủng hộ việc sắp xếp các hành động và đột biến mô hình lần lượt, vì "trạng thái điều khiển" của mô hình "kiểm soát" những hành động nào được bật và / hoặc tự động thực hiện bởi vị từ hành động tiếp theo. Bạn chỉ đơn giản là không thể dự đoán (nói chung) hệ thống sẽ ở trạng thái nào trước khi xử lý một hành động và do đó liệu hành động dự kiến ​​tiếp theo của bạn có được phép / có thể thực hiện hay không.

Ví dụ như mã,

export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

sẽ không được phép với SAM, vì thực tế là hành động hideNotification có thể được gửi đi phụ thuộc vào mô hình chấp nhận thành công giá trị "showNotication: true". Có thể có các phần khác của mô hình ngăn nó chấp nhận nó và do đó, sẽ không có lý do gì để kích hoạt hành động hideNotification.

Tôi thực sự khuyên bạn nên triển khai một vị từ hành động tiếp theo thích hợp sau khi cập nhật cửa hàng và trạng thái kiểm soát mới của mô hình có thể được biết. Đó là cách an toàn nhất để thực hiện hành vi mà bạn đang tìm kiếm.

Bạn có thể tham gia với chúng tôi trên Gitter nếu bạn muốn. Cũng có một hướng dẫn bắt đầu SAM có sẵn ở đây .

21
Jeff Barczewski 2016-08-25 15:15.

Sau khi thử các cách tiếp cận phổ biến khác nhau (người tạo hành động, côn đồ, sagas, sử thi, hiệu ứng, phần mềm trung gian tùy chỉnh), tôi vẫn cảm thấy rằng có thể có chỗ để cải thiện vì vậy tôi đã ghi lại hành trình của mình trong bài viết blog này, Tôi đặt logic kinh doanh của mình vào đâu một ứng dụng React / Redux?

Giống như các cuộc thảo luận ở đây, tôi đã cố gắng đối chiếu và so sánh các cách tiếp cận khác nhau. Cuối cùng, nó dẫn tôi đến việc giới thiệu một thư viện redux-logic mới lấy cảm hứng từ sử thi, sagas, phần mềm trung gian tùy chỉnh.

Nó cho phép bạn chặn các hành động để xác thực, xác minh, ủy quyền, cũng như cung cấp cách thực hiện IO không đồng bộ.

Một số chức năng phổ biến có thể được khai báo đơn giản như gỡ lỗi, điều chỉnh, hủy và chỉ sử dụng phản hồi từ yêu cầu mới nhất (takeLatest). redux-logic bao bọc mã của bạn, cung cấp chức năng này cho bạn.

Điều đó giúp bạn thực hiện logic kinh doanh cốt lõi của mình theo bất kỳ cách nào bạn muốn. Bạn không cần phải sử dụng thiết bị quan sát hoặc máy phát điện trừ khi bạn muốn. Sử dụng các hàm và lệnh gọi lại, lời hứa, hàm không đồng bộ (async / await), v.v.

Mã để thực hiện một thông báo 5s đơn giản sẽ như sau:

const notificationHide = createLogic({
  // the action type that will trigger this logic
  type: 'NOTIFICATION_DISPLAY',
  
  // your business logic can be applied in several
  // execution hooks: validate, transform, process
  // We are defining our code in the process hook below
  // so it runs after the action hit reducers, hide 5s later
  process({ getState, action }, dispatch) {
    setTimeout(() => {
      dispatch({ type: 'NOTIFICATION_CLEAR' });
    }, 5000);
  }
});
    

Tôi có một ví dụ thông báo nâng cao hơn trong repo của mình hoạt động tương tự như những gì Sebastian Lorber đã mô tả trong đó bạn có thể giới hạn hiển thị ở N mục và xoay qua bất kỳ mục nào được xếp hàng đợi. ví dụ về thông báo logic redux

Tôi có nhiều ví dụ trực tiếp về redux-logic jsfiddle cũng như các ví dụ đầy đủ . Tôi đang tiếp tục làm việc trên các tài liệu và ví dụ.

Tôi rất muốn nghe phản hồi của bạn.

10
cnexans 2017-04-28 16:52.

Tôi hiểu rằng câu hỏi này hơi cũ nhưng tôi sẽ giới thiệu một giải pháp khác bằng cách sử dụng redux-Observable aka. Sử thi.

Trích dẫn tài liệu chính thức:

Redux-Observable là gì?

Phần mềm trung gian dựa trên RxJS 5 cho Redux. Soạn và hủy các hành động không đồng bộ để tạo ra các hiệu ứng phụ và hơn thế nữa.

Epic là nguyên thủy cốt lõi của redux-có thể quan sát được.

Nó là một hàm nhận một luồng hành động và trả về một luồng hành động. Hành động trong, hành động ra.

Nói một cách dễ hiểu, bạn có thể tạo một hàm nhận các hành động thông qua Luồng và sau đó trả về một luồng hành động mới (sử dụng các tác dụng phụ phổ biến như hết thời gian, chậm trễ, khoảng thời gian và yêu cầu).

Hãy để tôi đăng mã và sau đó giải thích thêm một chút về nó

store.js

import {createStore, applyMiddleware} from 'redux'
import {createEpicMiddleware} from 'redux-observable'
import {Observable} from 'rxjs'
const NEW_NOTIFICATION = 'NEW_NOTIFICATION'
const QUIT_NOTIFICATION = 'QUIT_NOTIFICATION'
const NOTIFICATION_TIMEOUT = 2000

const initialState = ''
const rootReducer = (state = initialState, action) => {
  const {type, message} = action
  console.log(type)
  switch(type) {
    case NEW_NOTIFICATION:
      return message
    break
    case QUIT_NOTIFICATION:
      return initialState
    break
  }

  return state
}

const rootEpic = (action$) => {
  const incoming = action$.ofType(NEW_NOTIFICATION)
  const outgoing = incoming.switchMap((action) => {
    return Observable.of(quitNotification())
      .delay(NOTIFICATION_TIMEOUT)
      //.takeUntil(action$.ofType(NEW_NOTIFICATION))
  });

  return outgoing;
}

export function newNotification(message) {
  return ({type: NEW_NOTIFICATION, message})
}
export function quitNotification(message) {
  return ({type: QUIT_NOTIFICATION, message});
}

export const configureStore = () => createStore(
  rootReducer,
  applyMiddleware(createEpicMiddleware(rootEpic))
)

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {configureStore} from './store.js'
import {Provider} from 'react-redux'

const store = configureStore()

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

App.js

import React, { Component } from 'react';
import {connect} from 'react-redux'
import {newNotification} from './store.js'

class App extends Component {

  render() {
    return (
      <div className="App">
        {this.props.notificationExistance ? (<p>{this.props.notificationMessage}</p>) : ''}
        <button onClick={this.props.onNotificationRequest}>Click!</button>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    notificationExistance : state.length > 0,
    notificationMessage : state
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onNotificationRequest: () => dispatch(newNotification(new Date().toDateString()))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

Mã khóa để giải quyết vấn đề này dễ như ăn bánh mà bạn có thể thấy, thứ duy nhất xuất hiện khác với các câu trả lời khác là hàm rootEpic.

Điểm 1. Như với sagas, bạn phải kết hợp các sử thi để có được một hàm cấp cao nhất nhận một luồng hành động và trả về một luồng hành động, vì vậy bạn có thể sử dụng nó với nhà máy phần mềm trung gian createEpicMiddleware . Trong trường hợp của chúng tôi, chúng tôi chỉ cần một vì vậy chúng tôi chỉ có rootEpic của chúng tôi để chúng tôi không phải kết hợp bất cứ thứ gì nhưng thật tốt khi biết sự thật.

Điểm 2. RootEpic của chúng tôi xử lý logic các tác dụng phụ chỉ mất khoảng 5 dòng mã, thật tuyệt vời! Bao gồm cả thực tế là khá nhiều tuyên bố!

Điểm 3. Từng dòng từng dòng Lời giải thích (trong phần bình luận)

const rootEpic = (action$) => {
  // sets the incoming constant as a stream 
  // of actions with  type NEW_NOTIFICATION
  const incoming = action$.ofType(NEW_NOTIFICATION)
  // Merges the "incoming" stream with the stream resulting for each call
  // This functionality is similar to flatMap (or Promise.all in some way)
  // It creates a new stream with the values of incoming and 
  // the resulting values of the stream generated by the function passed
  // but it stops the merge when incoming gets a new value SO!,
  // in result: no quitNotification action is set in the resulting stream
  // in case there is a new alert
  const outgoing = incoming.switchMap((action) => {
    // creates of observable with the value passed 
    // (a stream with only one node)
    return Observable.of(quitNotification())
      // it waits before sending the nodes 
      // from the Observable.of(...) statement
      .delay(NOTIFICATION_TIMEOUT)
  });
  // we return the resulting stream
  return outgoing;
}

Tôi hy vọng nó sẽ giúp!

9
Vanuan 2017-11-27 17:18.

Tại sao phải khó như vậy? Đó chỉ là logic giao diện người dùng. Sử dụng một hành động chuyên dụng để đặt dữ liệu thông báo:

dispatch({ notificationData: { message: 'message', expire: +new Date() + 5*1000 } })

và một thành phần chuyên dụng để hiển thị nó:

const Notifications = ({ notificationData }) => {
    if(notificationData.expire > this.state.currentTime) {
      return <div>{notificationData.message}</div>
    } else return null;
}

Trong trường hợp này, các câu hỏi sẽ là "làm thế nào để bạn xóa trạng thái cũ?", "Làm thế nào để thông báo cho một thành phần rằng thời gian đã thay đổi"

Bạn có thể triển khai một số hành động TIMEOUT được gửi trên setTimeout từ một thành phần.

Có lẽ bạn chỉ cần làm sạch nó bất cứ khi nào một thông báo mới được hiển thị.

Dù sao thì cũng nên có một số setTimeoutở đâu đó, phải không? Tại sao không làm điều đó trong một thành phần

setTimeout(() => this.setState({ currentTime: +new Date()}), 
           this.props.notificationData.expire-(+new Date()) )

Động lực là chức năng "thông báo mờ dần" thực sự là một mối quan tâm về giao diện người dùng. Vì vậy, nó đơn giản hóa việc kiểm tra logic kinh doanh của bạn.

Có vẻ như không có ý nghĩa khi kiểm tra cách nó được triển khai. Việc xác minh thời điểm thông báo hết thời gian chỉ có ý nghĩa. Do đó, ít mã để sơ khai hơn, kiểm tra nhanh hơn, mã sạch hơn.

7
Yash 2016-09-16 03:24.

Nếu bạn muốn xử lý thời gian chờ trên các hành động chọn lọc, bạn có thể thử phương pháp tiếp cận phần mềm trung gian . Tôi gặp phải vấn đề tương tự khi xử lý các hành động dựa trên lời hứa một cách có chọn lọc và giải pháp này linh hoạt hơn.

Cho phép bạn nói rằng người tạo hành động của bạn trông như thế này:

//action creator
buildAction = (actionData) => ({
    ...actionData,
    timeout: 500
})

thời gian chờ có thể giữ nhiều giá trị trong hành động trên

  • số tính bằng mili giây - cho một khoảng thời gian chờ cụ thể
  • true - trong khoảng thời gian chờ không đổi. (được xử lý trong phần mềm trung gian)
  • không xác định - để gửi ngay lập tức

Việc triển khai phần mềm trung gian của bạn sẽ trông giống như sau:

//timeoutMiddleware.js
const timeoutMiddleware = store => next => action => {

  //If your action doesn't have any timeout attribute, fallback to the default handler
  if(!action.timeout) {
    return next (action)
  }

  const defaultTimeoutDuration = 1000;
  const timeoutDuration = Number.isInteger(action.timeout) ? action.timeout || defaultTimeoutDuration;

//timeout here is called based on the duration defined in the action.
  setTimeout(() => {
    next (action)
  }, timeoutDuration)
}

Bây giờ bạn có thể định tuyến tất cả các hành động của mình thông qua lớp phần mềm trung gian này bằng cách sử dụng redux.

createStore(reducer, applyMiddleware(timeoutMiddleware))

Bạn có thể tìm thấy một số ví dụ tương tự ở đây

7
Alireza 2017-04-15 04:17.

Cách thích hợp để thực hiện việc này là sử dụng Redux Thunk , một phần mềm trung gian phổ biến cho Redux, theo tài liệu Redux Thunk:

"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. Phần mềm thunk có thể được sử dụng để trì hoãn việc gửi 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ữ cử và getState dưới dạng tham số ".

Vì vậy, về cơ bản, nó trả về một hàm và bạn có thể trì hoãn việc gửi hoặc đặt nó ở trạng thái điều kiện.

Vì vậy, một cái gì đó như thế này sẽ thực hiện công việc cho bạn:

import ReduxThunk from 'redux-thunk';

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 5000);
  };
}
4
Bloomca 2017-06-05 05:16.

Bản thân Redux là một thư viện khá dài, và đối với những thứ như vậy, bạn sẽ phải sử dụng một thứ gì đó như Redux-thunk , nó sẽ cung cấp một dispatchhàm, vì vậy bạn sẽ có thể gửi thông báo đóng sau vài giây.

Tôi đã tạo một thư viện để giải quyết các vấn đề như độ dài và khả năng kết hợp, và ví dụ của bạn sẽ giống như sau:

import { createTile, createSyncTile } from 'redux-tiles';
import { sleep } from 'delounce';

const notifications = createSyncTile({
  type: ['ui', 'notifications'],
  fn: ({ params }) => params.data,
  // to have only one tile for all notifications
  nesting: ({ type }) => [type],
});

const notificationsManager = createTile({
  type: ['ui', 'notificationManager'],
  fn: ({ params, dispatch, actions }) => {
    dispatch(actions.ui.notifications({ type: params.type, data: params.data }));
    await sleep(params.timeout || 5000);
    dispatch(actions.ui.notifications({ type: params.type, data: null }));
    return { closed: true };
  },
  nesting: ({ type }) => [type],
});

Vì vậy, chúng tôi soạn các hành động đồng bộ hóa để hiển thị thông báo bên trong hành động không đồng bộ, có thể yêu cầu một số thông tin nền hoặc kiểm tra sau đó xem thông báo có được đóng theo cách thủ công hay không.

4
Mohmmad Ebrahimi Aval 2018-05-12 04:21.

Nó đơn giản. Sử dụng gói trim-redux và viết như thế này ở componentDidMountnơi khác và giết nó trong componentWillUnmount.

componentDidMount() {
  this.tm = setTimeout(function() {
    setStore({ age: 20 });
  }, 3000);
}

componentWillUnmount() {
  clearTimeout(this.tm);
}

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