RactiveJS + Redux 调度动作和水合物

Orb*_*tum 5 javascript ractivejs redux react-redux

我正在尝试使用 Redux 为小示例应用程序设置 RactiveJS - 初始化仪表板(来自 AJAX),从仪表板添加/删除元素(小部件)(并在服务器上保存序列化数据)。因为有几乎专门针对 React 的教程,所以我需要建议。我遵循了一些并获得了目录结构,例如:

views
    app.html
    dashboard.html
    widget.html
js
    actions
        DashboardActions.js
    components
        Dashboard.js
        Widget.js
    constants
        ActionTypes.js
    reducers
        dashboard.js
        index.js
    app.js
index.html
Run Code Online (Sandbox Code Playgroud)

这个例子有效,但有几个问题,我想弄清楚如何使它更好。例如:

1)如何传递(我应该传递?)存储和操作到 Ractive 组件树?现在它bindActionCreators在每个组件中使用,我认为这不是一个好的解决方案。

2) 将服务器的初始状态水化放在哪里?目前它是硬编码的reducers/dashboard.js,但我想使用后端作为数据源和数据保存端点。有中间件方法,但如果这是好的做法,那么如何将其应用到 RactiveJs 中?

3)我应该使用一个大的reducer还是每个组件一个reducer

4)也许核心概念不正确,应该重构?

视图/app.html

<Dashboard dashboard={{store.getState()}} store="{{store}}"></Dashboard>
Run Code Online (Sandbox Code Playgroud)

视图/dashboard.html

{{#with dashboard}}
<pre>
====
<a on-click="@this.addWidget('Added by click')" href="#">Add New</a>
{{#dashboard}}
    {{#each widgets}}
    <Widget id="{{this.id}}" name="{{this.name}}" size="{{this.size}}" actions="{{actions}}" store="{{store}}"></Widget>
    {{/each}}
{{/dashboard}}
====
</pre>
{{/with}}
Run Code Online (Sandbox Code Playgroud)

视图/小部件.html

<div>{{id}}-{{name}} (Size: {{size}})<a href="#" on-click="@this.deleteWidget(id)">X</a></div>
Run Code Online (Sandbox Code Playgroud)

动作/仪表板Actions.js

import * as types from '../constants/ActionTypes';

// Add widget to dashboard
export function addWidget(name) {
    return {
        type: types.ADD_WIDGET,
        name
    };
}

// Delete widget from dashboard
export function deleteWidget(id) {
    return {
        type: types.DELETE_WIDGET,
        id
    };
}
Run Code Online (Sandbox Code Playgroud)

组件/仪表板.js

import Ractive from 'ractive'
import * as DashboardActions from '../actions/DashboardActions';
import { dispatch, bindActionCreators } from 'redux'
import Widget from './Widget'
import template from '../../views/dashboard.html';

export default Ractive.extend({
    isolated: true,
    components: {
        Widget
    },

    oninit() {
        const store = this.get("store");
        const actions = bindActionCreators(DashboardActions, store.dispatch);

        this.set("actions", actions);
    },

    addWidget(name) {
        this.get("actions").addWidget(name);
    },

    template: template
});
Run Code Online (Sandbox Code Playgroud)

组件/Widget.js

import Ractive from 'ractive'
import * as DashboardActions from '../actions/DashboardActions';
import { dispatch, bindActionCreators } from 'redux'
import template from '../../views/widget.html';


export default Ractive.extend({
    isolated: true,
    template: template,
    oninit() {
        console.log(this.get("actions"));
        const store = this.get("store");
        const actions = bindActionCreators(DashboardActions, store.dispatch);

        this.set("actions", actions);
   },

    deleteWidget(id) {
       this.get("actions").deleteWidget(id);
    },
})
Run Code Online (Sandbox Code Playgroud)

常量/ActionTypes.js

// Add widget to dashboard
export const ADD_WIDGET = 'ADD_WIDGET';
// Delete widget from dashboard
export const DELETE_WIDGET = 'DELETE_WIDGET';
Run Code Online (Sandbox Code Playgroud)

减速器/dashboard.js

import * as types from '../constants/ActionTypes';

const initialState = {
    widgets: [
        {id: 1, name: "First widget"},
        {id: 2, name: "Second widget"},
        {id: 3, name: "Third widget"},
    ],
};

export default function dashboard(state = initialState, action) {
    switch (action.type) {
        case types.ADD_WIDGET:
            const newId = state.widgets.length + 1;
            const addedWidgets = [].concat(state.widgets, {
                id: newId,
                name: action.name
            });

            return {
                widgets: addedWidgets
            }

        case types.DELETE_WIDGET:
            const newWidgets = state.widgets.filter(function(obj) {
                return obj.id != action.id
            });

            return {
                widgets: newWidgets
            }

        default:
            return state;
    }
}
Run Code Online (Sandbox Code Playgroud)

减速器/ index.js

export { default as dashboard } from './dashboard';
Run Code Online (Sandbox Code Playgroud)

应用程序.js

import Ractive from 'ractive';
import template from '../views/app.html';
import Dashboard from './components/Dashboard.js'
import { createStore, combineReducers, bindActionCreators } from 'redux'
import * as reducers from './reducers'

const reducer = combineReducers(reducers);
const store = createStore(reducer);

let App = new Ractive({
    el: '#app',
    template: template,
    components: {
        Dashboard
    },
    data: {
        store
    }
});

store.subscribe(() => App.update());

export default App;
Run Code Online (Sandbox Code Playgroud)

谢谢!

Jos*_*eph 4

Ractive 没有强加任何关于如何完成此操作的约定。然而,Ractive 的设计与其他框架类似(生命周期挂钩、方法等)。因此,在其他框架上适用的方法也应该适用于 Ractive。

如何将存储和操作传递(我应该传递?)到 Ractive 组件树?目前它在每个组件中使用bindActionCreators,我认为这不是一个好的解决方案。

也许核心概念不正确,应该重构?

我很确定您很困惑是否将存储和操作直接分配给组件或通过祖先传递它们。答案是……两者都有。Redux 的作者实际上将组件分为两种:表示组件和容器组件。

总而言之,容器组件保存状态并调用操作。表示组件是无状态的,并且从祖先组件接收内容。

假设您有一个显示温度和状况的天气小部件。您将有 3 个组件:小部件组件本身、温度和条件。温度和条件组件都是演示性的。天气组件将成为获取数据的容器,将数据交给两个组件,并将 UI 交互转换为操作。

天气.js

// Assume the store is in store.js with actions already registered
import store from './path/to/store';
import Temperature from './path/to/Temperature';
import Conditions from './path/to/Conditions';

export default Ractive.extend({
  components: { Temperature, Conditions },
  template: `
    <div class="weather">
      <!-- pass in state data to presentational components -->
      <!-- call methods when events happen from components -->
      <Temperature value="{{ temperature }}" on-refresh="refreshTemp()" />
      <Conditions value="{{ conditions }}" on-refresh="refreshCond()" />
    </div>
  `,
  data: {
    temperature: null,
    conditions: null
  },
  oninit(){
    store.subscribe(() => {
      // Grab state and set it to component's local state
      // Assume the state is an object with temperature and
      // conditions properties.
      const { temperature, conditions } = store.getState();
      this.set({ temperature, conditions });
    });
  },
  // Call actions
  refreshTemp(){
    store.dispatch({ type: 'TEMPERATURE_REFRESH' }); 
  },
  refreshCond(){
    store.dispatch({ type: 'CONDITIONS_REFRESH' }); 
  }
});
Run Code Online (Sandbox Code Playgroud)

温度.js

// This component is presentational. It is not aware of Redux 
// constructs at all. It only knows that it accepts a value and
// should fire refresh.

export default Ractive.extend({
  template:`
    <div class="temperature">
      <span>The temperature is {{ value }}</span>
      <button type="button" on-click="refresh">Refresh</button>
    </div>
  `
});
Run Code Online (Sandbox Code Playgroud)

条件.js

// This component is presentational. It is not aware of Redux 
// constructs at all. It only knows that it accepts a value and
// should fire refresh.

export default Ractive.extend({
  template:`
    <div class="conditions">
      <img src="http://localhost/condition-images/{{ value }}.jpg">
      <button type="button" on-click="refresh">Refresh</button>
    </div>
  `
});
Run Code Online (Sandbox Code Playgroud)

将服务器的初始状态水化放在哪里?

如果我没记错的话,我看到的一个同构工作流程涉及将服务器提供的状态放入一个精心命名的全局变量中。应用程序启动时,应用程序会获取该全局中的数据并将其输入到商店中。Ractive 不参与此过程。

这将由您的服务器打印在页面上:

<script>
window.__APP_INITIAL_STATE__ = {...};
</script>
Run Code Online (Sandbox Code Playgroud)

然后,当您启动应用程序时,您可以使用该初始状态创建一个商店:

import { createStore } from 'redux'
import reducers from './reducers'
let store = createStore(reducers, window.__APP_INITIAL_STATE__);
Run Code Online (Sandbox Code Playgroud)

我应该使用一台大型减速机还是每个组件一台减速机?

Redux 对于如何拆分减速器以及如何标准化状态形状有很好的指南。一般来说,状态形状不是由组件定义的,而是由功能定义的。