2017-01-04 2 views
3

Я пытаюсь настроить RactiveJS с помощью Redux для небольшого примера приложения - инициализировать панель управления (из AJAX), добавлять/удалять элементы (виджеты) из панели мониторинга (и сохранять сериализованные данные на сервере). Поскольку для React есть учебники почти исключительно, мне нужен совет. Я последовал за некоторые и получили структуру каталогов, как:RactiveJS + Действия отправки и гидраты Redux

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 

Этот пример работает, но есть несколько проблем, и я хотел бы, чтобы выяснить, как сделать его лучше. Например:

1) Как пройти (и я должен пройти?) Хранилище и действия до дерева активных компонентов? В настоящее время он использует bindActionCreators в каждом компоненте, и я думаю, что это нехорошее решение.

2) Где положить начальную гидратацию состояния с сервера? В настоящее время он жестко закодирован в reducers/dashboard.js, но я хотел бы использовать backend в качестве источника данных и конечной точки сохранения данных. Существует подход промежуточного уровня, но если это хорошая практика, то как применить это с помощью RactiveJs?

3) Должен ли я использовать один большой reducer или по каждому компоненту один reducer?

4) Возможно, основная концепция неверна и должна быть реорганизована?

просмотров/app.html

<Dashboard dashboard={{store.getState()}} store="{{store}}"></Dashboard> 

просмотров/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}} 

просмотров/widget.html

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

действия/DashboardActions.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 
    }; 
} 

компоненты/Dashboard.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 
}); 

компоненты/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); 
    }, 
}) 

константы/ActionTypes.js

// Add widget to dashboard 
export const ADD_WIDGET = 'ADD_WIDGET'; 
// Delete widget from dashboard 
export const DELETE_WIDGET = 'DELETE_WIDGET'; 

редукторы/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; 
    } 
} 

редукторы/index.js

export { default as dashboard } from './dashboard'; 

app.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; 

Спасибо!

ответ

2

Ractive не устанавливает каких-либо договоренностей относительно того, как это делается. Тем не менее, Ractive спроектирован аналогично другим фреймворкам (крючки жизненного цикла, методы и т. Д.). Итак, то, что работает на вас в других рамках, должно также просто работать в Ractive.

Как пройти (и должен ли я пройти?) хранилище и действия до дерева активных компонентов? В настоящее время он использует bindActionCreators в каждом компоненте, и я думаю, что это нехорошее решение.

Возможно, основная концепция неверна и должна быть реорганизована?

Я уверен, что вы смущены, следует ли присваивать магазины и действия непосредственно компонентам или передавать их через своих предков. Ответ - оба. Автор Redux фактически splits components into 2 kinds: презентация и контейнеры.

В сущности компоненты контейнера сохраняют состояние и вызывают действия. Презентационные компоненты являются апатридами и получают материал от компонентов предков.

Скажем, у вас есть виджет погоды, который показывает температуру и условия. У вас будет 3 компонента, сам компонент виджета, температура и условия. Компоненты температуры и условий являются презентационными. Погодным компонентом будет контейнер, который захватывает данные, передает их обоим компонентам, а также преобразует взаимодействие пользовательского интерфейса в действия.

Weather.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' }); 
    } 
}); 

Temperature.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> 
    ` 
}); 

Conditions.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> 
    ` 
}); 

Где поставить начальное состояние гидратации от сервера?

Если я правильно помню, один изоморфный рабочий процесс, который я видел, включал помещение состояния, предоставленного сервером, в глобальную переменную с тщательно подобранным именем. При запуске приложения приложение собирает данные в этом глобальном и направляет их в хранилище. Ractive не участвует в этом процессе.

Это будет печататься вашим сервером на странице:

<script> 
window.__APP_INITIAL_STATE__ = {...}; 
</script> 

Тогда при загрузке приложения, вы создаете магазин с помощью этого начального состояния:

import { createStore } from 'redux' 
import reducers from './reducers' 
let store = createStore(reducers, window.__APP_INITIAL_STATE__); 

Должен ли я использовать один большой редуктор или по каждому компоненту один редуктор?

Redux имеет хорошее руководство по how to split up reducers, а также how to normalize state shape. В общем случае форма состояния не определяется компонентом, а скорее функциональностью.

+0

Спасибо! Можете ли вы дать мне несколько примеров кода, по крайней мере, для первого вопроса («Как передать (и должен ли я пройти?) Хранилище и действия до дерева активных компонентов? В настоящее время он использует bindActionCreators в каждом компоненте, и я думаю, что это нехорошее решение .')? – Orbitum

+0

@Orbitum ответ обновлен :) – Joseph

+0

Я принял ваш ответ, и это полезно, я реорганизовал код, как вы упомянули. Спасибо! – Orbitum

Смежные вопросы