如何在超时时分派 Redux 操作?

IT技术 javascript redux react-redux timeout
2021-02-06 17:29:54

我有一个更新应用程序通知状态的操作。通常,此通知将是某种错误或信息。然后我需要在 5 秒后调度另一个动作,将通知状态返回到初始状态,因此没有通知。这背后的主要原因是提供通知在 5 秒后自动消失的功能。

我在使用setTimeout和返回另一个操作方面没有运气,也找不到在线完成此操作的方式。因此,欢迎任何建议。

6个回答

不要陷入认为图书馆应该规定如何做所有事情陷阱如果你想在 JavaScript 中做一些超时的事情,你需要使用setTimeout. Redux 操作没有任何不同的理由。

Redux确实提供了一些处理异步内容的替代方法,但是只有当您意识到重复了太多代码时才应该使用这些方法。除非您遇到此问题,否则请使用该语言提供的内容并寻求最简单的解决方案。

内联编写异步代码

这是迄今为止最简单的方法。这里没有任何特定于 Redux 的内容。

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

类似地,从连接组件内部:

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

唯一的区别是,在连接的组件中,您通常无法访问商店本身,但可以将dispatch()特定的动作创建者作为props注入。然而,这对我们来说没有任何区别。

如果您不喜欢在从不同组件分派相同操作时犯错,您可能希望提取操作创建者而不是内联分派操作对象:

// 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)

或者,如果您之前已将它们绑定到connect()

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

到目前为止,我们还没有使用任何中间件或其他高级概念。

提取 Async Action Creator

上述方法在简单情况下工作正常,但您可能会发现它有一些问题:

  • 它迫使您在任何想要显示通知的地方复制此逻辑。
  • 通知没有 ID,因此如果您足够快地显示两个通知,您就会遇到竞争条件。当第一个超时完成时,它会 dispatch HIDE_NOTIFICATION,错误地比超时后更早地隐藏第二个通知。

要解决这些问题,您需要提取一个函数来集中超时逻辑并分派这两个操作。它可能看起来像这样:

// 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)
}

现在组件可以使用showNotificationWithTimeout而无需复制此逻辑或具有不同通知的竞争条件:

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

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

为什么showNotificationWithTimeout()接受dispatch作为第一个参数?因为它需要将操作分派到商店。通常一个组件可以访问,dispatch但由于我们想要一个外部函数来控制分派,我们需要让它控制分派。

如果你有一个从某个module导出的单例存储,你可以直接导入它并dispatch直接在它上面:

// 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.')    

这看起来更简单,但我们不推荐这种方法我们不喜欢它的主要原因是因为它强制 store 成为单例这使得实现服务器渲染变得非常困难在服务器上,您会希望每个请求都有自己的存储,以便不同的用户获得不同的预加载数据。

单例存储也使测试更加困难。在测试动作创建者时,您不能再模拟商店,因为它们引用从特定module导出的特定真实商店。您甚至无法从外部重置其状态。

因此,虽然您在技术上可以从module导出单例存储,但我们不鼓励这样做。除非您确定您的应用程序永远不会添加服务器渲染,否则不要这样做。

回到之前的版本:

// 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.')    

这解决了逻辑重复的问题,并使我们免于竞争条件。

Thunk 中间件

对于简单的应用程序,该方法应该足够了。如果您对中间件感到满意,请不要担心它。

但是,在较大的应用程序中,您可能会发现它存在某些不便之处。

例如,我们不得不dispatch绕过似乎很不幸这使得分离容器和展示组件变得更加棘手,因为任何以上述方式异步调度 Redux 操作的组件都必须接受dispatch作为一个 prop 才能进一步传递它。你不能再绑定动作创建者,connect()因为showNotificationWithTimeout()它不是真正的动作创建者。它不返回 Redux 操作。

此外,记住哪些函数是同步动作创建者showNotification(),哪些是异步助手,例如showNotificationWithTimeout(). 您必须以不同的方式使用它们,并小心不要将它们相互混淆。

这是寻找一种方法来“合法化”这种提供dispatch给辅助函数的模式的动机,并帮助 Redux“看到”这样的异步动作创建者作为正常动作创建者的特例,而不是完全不同的功能。

如果您仍然与我们在一起,并且您也发现您的应用程序存在问题,欢迎您使用Redux Thunk中间件。

总而言之,Redux Thunk 教 Redux 识别实际上是函数的特殊类型的操作:

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)
})

启用此中间件后,如果您调度一个函数,Redux Thunk 中间件会将其dispatch作为参数。它也会“吞下”这样的动作,所以不要担心你的减速器接收到奇怪的函数参数。你的 reducer 只会接收普通的对象动作——直接发出,或者由我们刚刚描述的函数发出。

这看起来不是很有用,是吗?不是在这种特殊情况下。然而,它让我们声明showNotificationWithTimeout()为一个普通的 Redux 动作创建者:

// 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)
  }
}

请注意该函数与我们在上一节中编写的函数几乎相同。但是它不接受dispatch作为第一个参数。相反,它返回一个接受dispatch作为第一个参数的函数。

我们将如何在我们的组件中使用它?当然,我们可以这样写:

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

我们正在调用 async action creator 来获取想要的内部函数,dispatch然后我们通过dispatch.

不过这比原版还要别扭!我们为什么要走那条路?

因为我之前告诉你的。如果启用了 Redux Thunk 中间件,则任何时候您尝试调度函数而不是操作对象时,中间件都会调用该函数,并将dispatch方法本身作为第一个参数

所以我们可以这样做:

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

最后,分派异步操作(实际上,一系列操作)与将单个操作同步分派到组件看起来没有什么不同。这很好,因为组件不应该关心某些事情是同步发生还是异步发生。我们只是把它抽象出来。

请注意,由于我们“教”了 Redux 识别此类“特殊”动作创建器(我们称它们为thunk动作创建器),因此我们现在可以在使用常规动作创建器的任何地方使用它们。例如,我们可以将它们用于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)

Thunks 中的读取状态

通常,您的减速器包含用于确定下一个状态的业务逻辑。然而,reducer 只在动作被分派后才开始。如果您在 thunk 动作创建器中有副作用(例如调用 API),并且您想在某些情况下阻止它怎么办?

不使用 thunk 中间件,您只需在组件内部执行此检查:

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

然而,提取动作创建者的目的是在许多组件中集中这种重复的逻辑。幸运的是,Redux Thunk 为您提供了一种读取Redux 存储当前状态的方法。除了dispatch,它还getState作为第二个参数传递给您从 thunk 动作创建者返回的函数。这让 thunk 可以读取存储的当前状态。

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)
  }
}

不要滥用这种模式。当有可用的缓存数据时,它有助于避免 API 调用,但它不是构建业务逻辑的良好基础。如果您getState()仅用于有条件地调度不同的操作,请考虑将业务逻辑放入 reducer。

下一步

现在您对 thunk 的工作方式有了基本的了解,请查看使用它们的Redux async 示例

你可能会发现很多 thunk 返回 Promise 的例子。这不是必需的,但可能非常方便。Redux 不关心你从 thunk 返回什么,但它从dispatch(). 这就是为什么您可以从 thunk 返回一个 Promise 并通过调用dispatch(someThunkReturningPromise()).then(...).

您还可以将复杂的 thunk 动作创建器拆分为几个较小的 thunk 动作创建器。dispatchthunk 提供方法可以接受 thunk 本身,因此您可以递归地应用该模式。同样,这对 Promise 最有效,因为您可以在其之上实现异步控制流。

对于某些应用程序,您可能会发现自己的异步控制流要求过于复杂,无法用 thunk 来表达。例如,重试失败的请求、使用令牌重新授权流程或分步引导在以这种方式编写时可能过于冗长且容易出错。在这种情况下,您可能需要查看更高级的异步控制流解决方案,例如Redux SagaRedux Loop评估它们,比较与您的需求相关的示例,然后选择您最喜欢的示例。

最后,如果您真的不需要它们,请不要使用任何东西(包括 thunk)。请记住,根据要求,您的解决方案可能看起来很简单

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

除非你知道你为什么要这样做,否则不要出汗。

这句话只适用于同步情况。例如,如果您编写,if (cond) dispatch({ type: 'A' }) else dispatch({ type: 'B' })也许您应该dispatch({ type: 'C', something: cond })选择忽略减速器中的操作,而不是根据action.something当前状态。
2021-03-10 17:29:54
异步操作似乎是对常见问题的简单而优雅的解决方案。为什么在不需要中间件的情况下不支持它们到 redux 中?这个答案可能会更加简洁。
2021-03-16 17:29:54
你能解释一下:*考虑将业务逻辑放入reducer*,这是否意味着我应该分派一个动作,然后在reducer中根据我的状态确定要分派哪些进一步的动作?我的问题是,然后我是否直接在我的减速器中分派其他动作,如果不是,那么我从哪里分派它们?
2021-03-21 17:29:54
@PhilMander 因为有许多替代模式,例如github.com/raisemarketplace/redux-loopgithub.com/yelouafi/redux-saga,它们同样(如果不是更多)优雅。Redux 是一个低级工具。您可以构建自己喜欢的超集并单独分发。
2021-03-24 17:29:54
@DanAbramov 你只是因为这个“除非你有这个问题,否则使用语言提供的东西并去寻找最简单的解决方案。” 后来才知道是谁写的!
2021-04-05 17:29:54

使用 Redux-saga

正如 Dan Abramov 所说,如果你想对异步代码进行更高级的控制,你可以看看redux-saga

这个答案是一个简单的例子,如果您想更好地解释为什么 redux-saga 对您的应用程序有用,请查看其他答案

总体思路是 Redux-saga 提供了一个 ES6 生成器解释器,它允许您轻松编写看起来像同步代码的异步代码(这就是为什么您经常会在 Redux-saga 中发现无限 while 循环的原因)。不知何故,Redux-saga 直接在 Javascript 中构建了自己的语言。Redux-saga 一开始可能会觉得有点难学,因为你需要对生成器有基本的了解,还要了解 Redux-saga 提供的语言。

我将尝试在这里描述我在 redux-saga 之上构建的通知系统。此示例目前在生产中运行。

高级通知系统规范

  • 您可以请求显示通知
  • 您可以请求隐藏通知
  • 通知的显示时间不应超过 4 秒
  • 可以同时显示多个通知
  • 不能同时显示超过 3 个通知
  • 如果在已显示 3 个通知时请求通知,则将其排队/推迟。

结果

我的生产应用程序Stample.co 的屏幕截图

敬酒

代码

在这里,我将通知命名为 a,toast但这是一个命名细节。

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)
    ]
}

和减速机:

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;
    }
};

用法

您可以简单地调度TOAST_DISPLAY_REQUESTED事件。如果你发送 4 个请求,只会显示 3 个通知,第 4 个会在第一个通知消失后出现。

请注意,我不特别推荐TOAST_DISPLAY_REQUESTED从 JSX调度您宁愿添加另一个 saga 来侦听您已经存在的应用程序事件,然后分派TOAST_DISPLAY_REQUESTED: 触发通知的组件,不必与通知系统紧密耦合。

结论

我的代码并不完美,但在生产环境中运行了几个月,并且有 0 个错误。Redux-saga 和生成器最初有点难,但是一旦你理解了它们,这种系统就很容易构建。

实现更复杂的规则甚至很容易,例如:

  • 当太多通知被“排队”时,为每个通知提供更少的显示时间,以便队列大小可以更快地减少。
  • 检测窗口大小变化,并相应地更改显示通知的最大数量(例如,桌面=3,手机纵向=2,手机横向=1)

老实说,祝你好运用 thunk 正确地实现这种东西。

请注意,您可以使用redux-observable做完全相同的事情,它与 redux-saga 非常相似。这几乎是一样的,是生成器和 RxJS 之间的品味问题。

操作:有效负载/事件到转换状态。Reducers:状态转换函数。组件:反映状态的用户界面。但是缺少一个主要部分 - 您如何管理许多转换的过程,这些转换都有自己的逻辑来确定接下来要执行的转换?还原传奇!
2021-03-16 17:29:54
我希望您在提出问题时能早点回答,因为我完全同意将 Saga 副作用库用于这样的业务逻辑。Reducers 和 Action Creators 用于状态转换。工作流与状态转换功能不同。工作流逐步过渡,但本身不是过渡。Redux + React 本身缺乏这一点——这正是 Redux Saga 如此有用的原因。
2021-03-22 17:29:54
确切地。Actions & Reducers 都是状态机的一部分。有时,对于复杂的工作流,您需要一些其他东西来编排状态机,而这不是状态机本身的直接组成部分!
2021-03-26 17:29:54
@mrbrdo 如果您仔细阅读我的回答,您会注意到通知超时实际上是通过以下方式处理的yield call(delay,timeoutValue);:它不是相同的 API,但具有相同的效果
2021-03-28 17:29:54
谢谢,我尽我最大的努力让 redux-saga 流行,因为这些原因:) 很少有人认为目前 redux-saga 只是 thunk 的替代品,并且没有看到 redux-saga 如何实现复杂和解耦的工作流程
2021-04-02 17:29:54

包含示例项目的存储库

目前有四个示例项目:

  1. 内联编写异步代码
  2. 提取 Async Action Creator
  3. 使用 Redux Thunk
  4. 使用 Redux Saga

接受的答案很棒。

但是还缺少一些东西:

  1. 没有可运行的示例项目,只有一些代码片段。
  2. 没有其他替代方案的示例代码,例如:
    1. Redux 传奇

所以我创建了Hello Async存储库来添加缺少的东西:

  1. 可运行的项目。您无需修改​​即可下载并运行它们。
  2. 提供更多替代方案的示例代码:

Redux 传奇

已接受的答案已经为 Async Code Inline、Async Action Generator 和 Redux Thunk 提供了示例代码片段。为了完整起见,我提供了 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 }
}

动作简单而纯粹。

// component.js

import { connect } from 'react-redux'

// ...

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

// ...

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

组件没有什么特别之处。

// 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 基于ES6 生成器

// index.js

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

const sagaMiddleware = createSagaMiddleware()

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

sagaMiddleware.run(saga)

与 Redux Thunk 相比

优点

  • 你不会最终陷入回调地狱。
  • 您可以轻松测试异步流程。
  • 你的行为保持纯洁。

缺点

  • 它依赖于相对较新的 ES6 Generators。

如果上面的代码片段没有回答您的所有问题,请参阅可运行项目

你可以用redux-thunk做到这一点redux 文档中有一个关于异步操作指南,比如 setTimeout。

@Ilja 这应该有效: const store = createStore(reducer, applyMiddleware([ReduxPromise, thunk]));
2021-03-25 17:29:54
只是一个快速的后续问题,当使用中间件时applyMiddleware(ReduxPromise, thunk)(createStore),这就是您添加几个中间件(昏迷分隔?)的方式,因为我似乎无法让 thunk 工作。
2021-04-04 17:29:54

我还建议您查看SAM 模式

SAM 模式提倡包括一个“下一个动作谓词”,其中(自动)动作,例如“通知在 5 秒后自动消失”,一旦模型更新(SAM 模型~reducer 状态 + 存储)就会触发。

该模式提倡一次一个地对动作和模型突变进行排序,因为模型的“控制状态”“控制”了下一个动作谓词启用和/或自动执行哪些动作。您根本无法预测(通常)系统在处理操作之前将处于什么状态,因此您的下一个预期操作是否会被允许/可能。

所以例如代码,

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

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

SAM 不允许,因为可以调度 hideNotification 操作的事实取决于模型成功接受值“showNotification: true”。模型的其他部分可能会阻止它接受它,因此没有理由触发 hideNotification 操作。

我强烈建议在商店更新并且可以知道模型的新控制状态之后实施适当的下一步操作谓词。这是实现您正在寻找的行为的最安全的方法。

如果你愿意,你可以在 Gitter 上加入我们。这里还有一份SAM 入门指南

SAM 是一个真正的软件工程模式(刚刚用它制作了一个 Alexa SDK)。它基于 TLA+,并试图将这项令人难以置信的工作的力量带给每个开发人员。SAM 纠正了(几乎)每个人几十年来一直在使用的三个近似值: - 动作可以操纵应用程序状态 - 赋值等同于变异 - 没有对编程步骤是什么的精确定义(例如是 a = b * ca 步骤, 是 1/ 读取 b,c 2/ 计算 b*c, 3/ 将结果分配为三个不同的步骤吗?
2021-03-11 17:29:54
到目前为止,我只是触及了表面,但已经对 SAM 模式感到兴奋。V = S( vm( M.present( A(data) ) ), nap(M))只是美丽。感谢您分享您的想法和经验。我会深入挖掘。
2021-03-19 17:29:54
@ftor,谢谢!第一次写的时候也有这种感觉。我已经在生产中使用 SAM 将近一年了,我想不出有什么时候我觉得我需要一个库来实现 SAM(甚至是 vdom,但我可以看到它什么时候可以使用)。只需一行代码,就是这样!SAM 生成同构代码,如何处理异步调用没有歧义......我想不出我在做什么的时候,我在做什么?
2021-04-05 17:29:54