如何结合聚合物还原和<app-route>?

时间:2017-01-03 09:32:52

标签: polymer redux polymer-1.0

我们目前正在使用<app-route>进行路由,现在正在使用polymer-redux实施Redux。但是,目前还不清楚将两者结合起来的最佳方法是什么。由于<app-route>维持自己的状态,我们无法将其存储在Redux商店中。但是,对于用户可以执行的某些操作,我们还希望更新URL。

我目前的想法是使用中间件做某些事情,但我不清楚如何最好地从该中间件中访问/修改<app-route>中的路由。我们怎样才能最好地接近这个?

2 个答案:

答案 0 :(得分:4)

我自己遇到了同样的问题。冲突是redux的主要原则是它倡导一种独特的集中式状态,而聚合物主张多个分散式状态。调和两者显然需要不时进行一些黑客攻击。通过聚合物(与app-route一起使用)将redux状态的浏览器URL同步是一个很好的例子。使用app-location,我所做的就是:

<!-- my-app.html -->
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="/bower_components/app-route/app-location.html">
<link rel="import" href="/src/redux/redux-behavior.html">
<link rel="import" href="/src/redux/route-action-creator.html">

<dom-module id="my-app">
  <template>
    <app-location route="{{_browserRoute}}"></app-location>
  </template>

  <script>
    Polymer({
      is: 'my-app',

      behaviors: [ReduxBehavior, RouteActionCreator],

      properties: {
        _browserRoute: {
          type: Object
        }
      },

      observers: [
        '_browserRouteChanged(_browserRoute.*)'
      ],

      _browserRouteChanged: function(browserRoute) {
        this.dispatch('browsed', browserRoute.base)
      }
    })
  </script>
</dom-module>

我们使用双向绑定来保持my-app的_browserRoute与浏览器网址同步。更改后,它会调度“已浏览”操作,该操作会将状态中的当前路由减少为_browserRoute的新值。

<!-- route-action-creator.html -->
<script>
  const ROUTE_ACTIONS = {}
  const routeActions = {}

  ROUTE_ACTIONS.BROWSED = 'ROUTE_BROWSED'
  routeActions.browsed = function(route) {
    return {
      type: ROUTE_ACTIONS.BROWSED,
      route: route
    }
  }

  ROUTE_ACTIONS.REDIRECTED = 'ROUTE_REDIRECTED'
  routeActions.redirected = function(path) {
    window.history.pushState({}, null, path)
    window.dispatchEvent(new CustomEvent('location-changed'))
    return {
      type: ROUTE_ACTIONS.REDIRECTED,
      path: path
    }
  }

  /**
   * Route action creator behavior
   * @polymerBehavior
   */
  RouteActionCreator = {
    actions: routeActions
  }
</script>

<!-- route-reducer.html -->
<link rel="import" href="/src/redux/route-action-creator.html">
<script>
  const reducers = {}
  reducers.routeReducer = function(state, action) {
    if (!state) {
      return {
        current: null
      }
    }

    switch (action.type) {
      case ROUTE_ACTIONS.BROWSED:
        return Object.assign({}, state, {
          current: action.route
        })

      case ROUTE_ACTIONS.REDIRECTED:
        // Browser state is reduced in action creator because it uses an event
        return state

      default:
        return state
    }
  }
</script>

现在我可以绑定

  

statePath:'route.current'

到我的应用中的任何app-route组件。我也可以用

  

this.dispatch('redirected','/ some-path')

用于重定向和标准锚标记,因为浏览器将与状态同步。

答案 1 :(得分:2)

我会创建一个包含app-route的非可视聚合物元素,并实现polymer-redux行为并处理app-routeredux商店之间的互动。

以下是向导的这种元素的示例(包含要加载的步骤和分析):

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/app-route/app-location.html">
<link rel="import" href="../bower_components/app-route/app-route.html">

<dom-module id="my-router">
  <style>
  </style>
  <template>
      <app-location route="{{route}}"  id="location" use-hash-as-path></app-location>
      <app-route
            active="{{active}}"
            route="{{route}}"
            pattern="/:step/:id"
            data="{{page}}"
            tail="{{tail}}">
      </app-route>
  </template>
  <script>
    (function() {
      'use strict';
      Polymer({
        is: 'my-router',
        behaviors: [ ReduxBehavior ],
        properties: {
            page: {
                type:Object,
                value: function(){ return {};},
            },
            currentAnalysisId: {
                type:String,
                statePath: 'id',
            },
            currentStep: {
                type:String,
                statePath:'currentStep',
            },
            active:{
                type: Boolean,
                observer:'_onInValidRoute'
            }
        },
        observers: [
          '_changeRoute(page)',
          '_changeStepOrAnalysisId(currentStep,currentAnalysisId)',
        ],
        actions: {
             changeStep: function(step) {
                return {
                    type: 'CHANGE_CURRENT_STEP',
                    step,
                }
            },
            loadAnalysis: function(id,step)  {
                return {
                    type: 'LOAD_ANALYSIS',
                    id,
                    step,
                }
            },
        },
        _initialized : false,
        ready: function() {
            // required otherwise if navigating to a sub-route for the first time will be reset, by the localstorage initial redux state update
            this._initialized = true;
        },
        _changeRoute: function(page) {
            // required because otherwise
            this.debounce('route_update',()=>{
                let step = page.step;
                let id = page.id;

                if (id && this.getState().id !== id) {
                        ga('send', 'event', 'Analysis', 'load');
                        this.dispatch('loadAnalysis',id,step)
                }
                else if (this.getState().currentStep !== step) {
                    this.dispatch('changeStep',step);
                }
                ga('send', 'pageview');
            },1);
        },
        _changeStepOrAnalysisId: function(step, id) {
            if (!this._initialized) {
                return;
            }
            this.debounce('state_changed',()=>{
                if (this.page.step !== step || (this.page.id !== id && this.page.id !== '' && id !== null)) {
                    this.page = {step,id};
                }
            })
        },
        _onInValidRoute: function(valid) {
            if (!valid) {
                this.async(()=> {
                    this.$.location.set('route.path','/start/');
                })
            }
        },
      });
    })();
  </script>
</dom-module>