redux-observable 获取当前位置

redux-observable to get current location

我正在尝试使用 React Native Geolocation to getCurrentPosition and then as soon as the position is returned, use react native geocoder to use that position to get the location. I'm using redux-observable epics 来完成所有这些。

这是我的两个 epics:

location.epic.js

import { updateRegion } from '../map/map.action'
import Geocoder from 'react-native-geocoder'

export const getCurrentLocationEpic = action$ =>
  action$.ofType(GET_CURRENT_LOCATION)
    .mergeMap(() =>
      Observable.fromPromise(Geocoder.geocodePosition(makeSelectLocation()))
        .flatMap((response) => Observable.of(
          getCurrentLocationFulfilled(response)
        ))
        .catch(error => Observable.of(getCurrentLocationRejected(error)))
    )

export const getCurrentPositionEpic = action$ =>
  action$.ofType(GET_CURRENT_POSITION)
    .mergeMap(() =>
      navigator.geolocation.getCurrentPosition(
        (position) => Observable.of(
          updateRegion(position),
          getCurrentLocation(position)
        ),
        error => Observable.of(getCurrentPositionRejected(error)),
        { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
      ).do(x => console.log(x))
    ).do(x => console.log(x))

应用一启动,这段代码就会执行:

class Vepo extends Component {
  componentDidMount() {
    const { store } = this.context
    this.unsubscribe = store.subscribe(() => { })
    store.dispatch(fetchCategories())
    store.dispatch(getCurrentPosition())
  }

fetchCategories() 是一个也有史诗的动作,但它是有效的。调度 getCurrentPosition() 动作贯穿上面的史诗。我能看到的唯一输出是我的减速器处理 getLocationRejected() 因为它控制台记录这个:

there was an issue getting your current location:  Error: invalid position: {lat, lng} required
    at Object.geocodePosition (geocoder.js:15)
    at MergeMapSubscriber.project (location.epic.js:17)
    at MergeMapSubscriber._tryNext (mergeMap.js:120)
    at MergeMapSubscriber._next (mergeMap.js:110)
    at MergeMapSubscriber.Subscriber.next (Subscriber.js:89)
    at FilterSubscriber._next (filter.js:88)
    at FilterSubscriber.Subscriber.next (Subscriber.js:89)
    at Subject.next (Subject.js:55)
    at Object.dispatch (createEpicMiddleware.js:72)
    at Object.dispatch (devTools.js:313)

这是我的减速器:

const searchPage = (
  initialLocationState = initialState.get('searchForm').get('location'),
  action: Object): string => {
  switch (action.type) {
    case GET_CURRENT_LOCATION_FULFILLED: {
      return action.payload
    }
    case GET_CURRENT_LOCATION_REJECTED: {
      console.log('there was an issue getting your current location: ', 
        action.payload)
      return initialLocationState
    }
    case GET_CURRENT_POSITION_REJECTED: {
      console.log('there was an issue getting your current position: ', 
        action.payload)
      return initialLocationState
    }
    default:
      return initialLocationState
  }
}

有什么明显的我做错了吗?我通过添加 .do(x => console.log(x)) 进行调试的尝试没有任何作用,没有任何内容记录到控制台。 updateRegion() 永远不会触发,因为它会调度一个动作,而 reducer UPDATE_REGION 永远不会执行。但执行必须进入 getCurrentPosition() 的成功案例 eg:

(position) => Observable.of(
              updateRegion(position),
              getCurrentLocation(position)
            ),

必须执行,因为 getCurrentLocation(position) 确实被调度了。

我哪里错了?

What would be your technique for using an epic on a function which takes a callback function? getCurrentPosition() takes a callback and the callback handles the payload. Basically if you remove Observable.of( from inside getCurrentPosition(), that's how getCurrentPosition() is correctly used - and has been working for me without redux-observable.

在自定义 Observable 中包装任何东西非常简单,与创建 Promise 非常相似,除了 Observables 是惰性的——理解这一点很重要! RxJS Docs

在地理定位的情况下,有两个主要的 API,getCurrentPositionwatchPosition。它们具有相同的语义,只是 watchPosition 会在每次位置更改时调用您的成功回调,而不仅仅是一次。让我们使用那个,因为很自然地将它建模为 stream/Observable 并且最灵活。

function geolocationObservable(options) {
  return new Observable(observer => {
    // This function is called when someone subscribes.

    const id = navigator.geolocation.watchPosition(
      (position) => {
        observer.next(position);
      },
      error => {
        observer.error(error);
      },
      options
    );

    // Our teardown function. Will be called if they unsubscribe
    return () => {
      navigator.geolocation.clearWatch(id);
    };
  });
}

geolocationObservable({ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 })
  .subscribe(
    position => console.log(position),
    e => console.error(e)
  );
  // will log every time your location changes, until you unsubscribe

因为它现在是一个 Observable,如果你只想要当前位置,你可以 .take(1).

所以在你的史诗中使用它可能是这样的

// If you want, you could also use .share() to share a single
// underlying `watchPosition` subscription aka multicast, but
// that's outside the scope of the question so I don't include it
const currentPosition$ = geolocationObservable({
  enableHighAccuracy: true,
  timeout: 20000,
  maximumAge: 1000
});

export const getCurrentPositionEpic = action$ =>
  action$.ofType(GET_CURRENT_POSITION)
    .mergeMap(() =>
      currentPosition$
        .take(1) // <----------------------------- only the current position
        .mergeMap(position => Observable.of(
          updateRegion(position),
          getCurrentLocation(position)
        ))
        .catch(error => Observable.of(
          getCurrentPositionRejected(error)
        ))
    );

附带说明一下,您可能不需要同时发送 updateRegion()getCurrentLocation()。您的 reducer 是否可以只监听一个动作,因为它们似乎都在发出相同的意图?