mirror of
https://github.com/google-map-react/google-map-react.git
synced 2025-12-08 18:26:32 +00:00
Test rxjs instead of reconciler, add ability to draw layers, test performance
This commit is contained in:
parent
c4471ae1fa
commit
100c219e1e
@ -15,7 +15,7 @@ export const gMap = ({
|
||||
style, hoverDistance, options,
|
||||
mapParams: { center, zoom },
|
||||
onChange, onChildMouseEnter, onChildMouseLeave,
|
||||
markers, hoveredMarkerId,
|
||||
markers, // hoveredMarkerId,
|
||||
}) => (
|
||||
<GoogleMapReact
|
||||
style={style}
|
||||
@ -29,13 +29,6 @@ export const gMap = ({
|
||||
>
|
||||
{
|
||||
markers
|
||||
.map(({ ...markerProps, id }) => (
|
||||
<SimpleMarker
|
||||
key={id}
|
||||
hovered={id === hoveredMarkerId}
|
||||
{...markerProps}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</GoogleMapReact>
|
||||
);
|
||||
@ -66,7 +59,7 @@ export const gMapHOC = compose(
|
||||
)
|
||||
),
|
||||
withState('hoveredMarkerId', 'setHoveredMarkerId', -1),
|
||||
withState('mapParams', 'setMapParams', { center: susolvkaCoords, zoom: 10 }),
|
||||
withState('mapParams', 'setMapParams', { center: susolvkaCoords, zoom: 6 }),
|
||||
// describe events
|
||||
withHandlers({
|
||||
onChange: ({ setMapParams }) => ({ center, zoom, bounds }) => {
|
||||
@ -86,6 +79,19 @@ export const gMapHOC = compose(
|
||||
? markers.filter(m => ptInBounds(bounds, m))
|
||||
: [],
|
||||
})
|
||||
),
|
||||
withPropsOnChange(
|
||||
['markers'],
|
||||
({ markers }) => ({
|
||||
markers: markers
|
||||
.map(({ ...markerProps, id }) => (
|
||||
<SimpleMarker
|
||||
key={id}
|
||||
id={id}
|
||||
{...markerProps}
|
||||
/>
|
||||
)),
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
107
develop/GMapOptim.js
Normal file
107
develop/GMapOptim.js
Normal file
@ -0,0 +1,107 @@
|
||||
// Example to test the React Reconciler, and even this
|
||||
// is 100x faster in development mode, I see no difference
|
||||
// with NODE_ENV==='production'
|
||||
// so the simple example with same params at ./GMap.js works with same speed
|
||||
// works very well
|
||||
|
||||
import React from 'react';
|
||||
import compose from 'recompose/compose';
|
||||
import defaultProps from 'recompose/defaultProps';
|
||||
import withStateSelector from './utils/withStateSelector';
|
||||
import withHandlers from 'recompose/withHandlers';
|
||||
import withState from 'recompose/withState';
|
||||
import withPropsOnChange from 'recompose/withPropsOnChange';
|
||||
import ptInBounds from './utils/ptInBounds';
|
||||
import GoogleMapReact from '../src';
|
||||
// import SimpleMarker from './markers/SimpleMarker';
|
||||
import ReactiveMarker from './markers/ReactiveMarker';
|
||||
import { createSelector } from 'reselect';
|
||||
import { susolvkaCoords, generateMarkers } from './data/fakeData';
|
||||
import props2Stream from './utils/props2Stream';
|
||||
|
||||
export const gMap = ({
|
||||
style, hoverDistance, options,
|
||||
mapParams: { center, zoom },
|
||||
onChange, onChildMouseEnter, onChildMouseLeave,
|
||||
markers,
|
||||
}) => (
|
||||
<GoogleMapReact
|
||||
style={style}
|
||||
options={options}
|
||||
hoverDistance={hoverDistance}
|
||||
center={center}
|
||||
zoom={zoom}
|
||||
onChange={onChange}
|
||||
onChildMouseEnter={onChildMouseEnter}
|
||||
onChildMouseLeave={onChildMouseLeave}
|
||||
experimental
|
||||
>
|
||||
{markers}
|
||||
</GoogleMapReact>
|
||||
);
|
||||
|
||||
export const gMapHOC = compose(
|
||||
defaultProps({
|
||||
clusterRadius: 60,
|
||||
hoverDistance: 30,
|
||||
options: {
|
||||
minZoom: 3,
|
||||
maxZoom: 15,
|
||||
},
|
||||
style: {
|
||||
position: 'relative',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
flex: 1,
|
||||
},
|
||||
}),
|
||||
|
||||
// withState so you could change markers if you want
|
||||
withStateSelector(
|
||||
'markers',
|
||||
'setMarkers',
|
||||
() => createSelector(
|
||||
({ route: { markersCount = 20 } }) => markersCount,
|
||||
(markersCount) => generateMarkers(markersCount)
|
||||
)
|
||||
),
|
||||
withState('hoveredMarkerId', 'setHoveredMarkerId', -1),
|
||||
withState('mapParams', 'setMapParams', { center: susolvkaCoords, zoom: 6 }),
|
||||
// describe events
|
||||
withHandlers({
|
||||
onChange: ({ setMapParams }) => ({ center, zoom, bounds }) => {
|
||||
setMapParams({ center, zoom, bounds });
|
||||
},
|
||||
onChildMouseEnter: ({ setHoveredMarkerId }) => (hoverKey, { id }) => {
|
||||
setHoveredMarkerId(id);
|
||||
},
|
||||
onChildMouseLeave: ({ setHoveredMarkerId }) => () => {
|
||||
setHoveredMarkerId(-1);
|
||||
},
|
||||
}),
|
||||
withPropsOnChange(
|
||||
['markers', 'mapParams'],
|
||||
({ markers, mapParams: { bounds } }) => ({
|
||||
markers: bounds
|
||||
? markers.filter(m => ptInBounds(bounds, m))
|
||||
: [],
|
||||
})
|
||||
),
|
||||
props2Stream('hoveredMarkerId'),
|
||||
withPropsOnChange(
|
||||
['markers', 'hoveredMarkerId$'],
|
||||
({ markers, hoveredMarkerId$ }) => ({
|
||||
markers: markers
|
||||
.map(({ ...markerProps, id }) => (
|
||||
<ReactiveMarker
|
||||
key={id}
|
||||
id={id}
|
||||
hoveredMarkerId$={hoveredMarkerId$}
|
||||
{...markerProps}
|
||||
/>
|
||||
)),
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
export default gMapHOC(gMap);
|
||||
@ -12,6 +12,7 @@ export class Layout extends Component { // eslint-disable-line
|
||||
<header className={header}>
|
||||
<div className={links}>
|
||||
<Link to="/">Multi Markers</Link>
|
||||
<Link to="/hoverunoptim">Hover unoptim</Link>
|
||||
<Link to="/hoveroptim">Hover optim</Link>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@ -4,6 +4,7 @@ import { render } from 'react-dom';
|
||||
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
|
||||
import Layout from './Layout';
|
||||
import GMap from './GMap';
|
||||
import GMapOptim from './GMapOptim';
|
||||
|
||||
import 'normalize.css/normalize.css';
|
||||
import './Main.sass';
|
||||
@ -13,7 +14,8 @@ const mountNode = document.getElementById('app');
|
||||
render(
|
||||
<Router history={browserHistory}>
|
||||
<Route path="/" component={Layout}>
|
||||
<Route markersCount={200} path="hoveroptim" component={GMap} />
|
||||
<Route markersCount={500} path="hoverunoptim" component={GMap} />
|
||||
<Route markersCount={500} path="hoveroptim" component={GMapOptim} />
|
||||
<IndexRoute markersCount={20} component={GMap} />
|
||||
</Route>
|
||||
</Router>
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
import path from 'path'; // eslint-disable-line no-var
|
||||
import autoprefixer from 'autoprefixer'; // eslint-disable-line no-var
|
||||
import webpack from 'webpack';
|
||||
|
||||
export default {
|
||||
devtool: 'cheap-module-eval-source-map',
|
||||
postcss: [
|
||||
autoprefixer({ browsers: ['last 2 versions'] }),
|
||||
],
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(process.env.NODE_ENV
|
||||
? {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
}
|
||||
: {}
|
||||
),
|
||||
],
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
|
||||
@ -7,7 +7,7 @@ import { Motion, spring } from 'react-motion';
|
||||
import clusterMarkerStyles from './ClusterMarker.sass';
|
||||
|
||||
export const clusterMarker = ({
|
||||
styles, text, hovered,
|
||||
styles, text, hovered, $hover,
|
||||
defaultMotionStyle, motionStyle,
|
||||
}) => (
|
||||
<Motion
|
||||
@ -20,7 +20,7 @@ export const clusterMarker = ({
|
||||
className={styles.marker}
|
||||
style={{
|
||||
transform: `translate3D(0,0,0) scale(${scale}, ${scale})`,
|
||||
zIndex: hovered ? 1 : 0,
|
||||
zIndex: (hovered || $hover) ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@ -57,15 +57,16 @@ export const clusterMarkerHOC = compose(
|
||||
})
|
||||
),
|
||||
withPropsOnChange(
|
||||
['hovered'],
|
||||
['hovered', '$hover'],
|
||||
({
|
||||
hovered, hoveredScale, defaultScale,
|
||||
hovered, $hover, hoveredScale, defaultScale,
|
||||
stiffness, damping, precision,
|
||||
}) => ({
|
||||
$hover,
|
||||
hovered,
|
||||
motionStyle: {
|
||||
scale: spring(
|
||||
hovered ? hoveredScale : defaultScale,
|
||||
(hovered || $hover) ? hoveredScale : defaultScale,
|
||||
{ stiffness, damping, precision }
|
||||
),
|
||||
},
|
||||
|
||||
26
develop/markers/ReactiveMarker.js
Normal file
26
develop/markers/ReactiveMarker.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import compose from 'recompose/compose';
|
||||
import SimpleMarker from './SimpleMarker';
|
||||
import stream2Props from '../utils/stream2Props';
|
||||
import 'rxjs/add/operator/filter';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/scan';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
// import defaultProps from 'recompose/defaultProps';
|
||||
// import reactiveMarkerStyles from './reactiveMarker.scss';
|
||||
|
||||
export const reactiveMarker = (props) => (
|
||||
<SimpleMarker {...props} />
|
||||
);
|
||||
|
||||
export const reactiveMarkerHOC = compose(
|
||||
stream2Props(({ id, hoveredMarkerId$ }) => (
|
||||
hoveredMarkerId$
|
||||
.map(hoveredMarkerId => hoveredMarkerId === id)
|
||||
.distinctUntilChanged()
|
||||
.map(v => ({ hovered: v }))
|
||||
))
|
||||
);
|
||||
|
||||
export default reactiveMarkerHOC(reactiveMarker);
|
||||
@ -7,7 +7,7 @@ import { clusterMarkerHOC } from './ClusterMarker.js';
|
||||
import simpleMarkerStyles from './SimpleMarker.sass';
|
||||
|
||||
export const simpleMarker = ({
|
||||
styles, hovered,
|
||||
styles, hovered, $hover,
|
||||
defaultMotionStyle, motionStyle,
|
||||
}) => (
|
||||
<Motion
|
||||
@ -20,7 +20,7 @@ export const simpleMarker = ({
|
||||
className={styles.marker}
|
||||
style={{
|
||||
transform: `translate3D(0,0,0) scale(${scale}, ${scale})`,
|
||||
zIndex: hovered ? 1 : 0,
|
||||
zIndex: (hovered || $hover) ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
</div>
|
||||
@ -32,7 +32,7 @@ export const simpleMarker = ({
|
||||
export const simpleMarkerHOC = compose(
|
||||
defaultProps({
|
||||
styles: simpleMarkerStyles,
|
||||
initialScale: 0.3,
|
||||
initialScale: 0.6,
|
||||
defaultScale: 0.6,
|
||||
hoveredScale: 0.7,
|
||||
}),
|
||||
|
||||
18
develop/tests/playground.spec.js
Normal file
18
develop/tests/playground.spec.js
Normal file
@ -0,0 +1,18 @@
|
||||
/* eslint-disable max-len */
|
||||
// `npm bin`/mocha --compilers js:babel-register --require babel-polyfill --reporter min --watch './develop/**/*.spec.js'
|
||||
/* eslint-enable max-len */
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
|
||||
describe('Playground', () => {
|
||||
it('Play', async () => {
|
||||
const comparator = (a, b) => a === b;
|
||||
const props$ = (new BehaviorSubject(1))
|
||||
.distinctUntilChanged(comparator);
|
||||
|
||||
props$.subscribe(v => console.log(v));
|
||||
props$.next(1);
|
||||
props$.next(2);
|
||||
props$.next(1);
|
||||
});
|
||||
});
|
||||
13
develop/utils/omit.js
Normal file
13
develop/utils/omit.js
Normal file
@ -0,0 +1,13 @@
|
||||
// https://github.com/acdlite/recompose/blob/master/src/packages/recompose/utils/omit.js
|
||||
const omit = (obj, keys) => {
|
||||
const { ...rest } = obj;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (rest.hasOwnProperty(key)) {
|
||||
delete rest[key];
|
||||
}
|
||||
}
|
||||
return rest;
|
||||
};
|
||||
|
||||
export default omit;
|
||||
14
develop/utils/pick.js
Normal file
14
develop/utils/pick.js
Normal file
@ -0,0 +1,14 @@
|
||||
// https://github.com/acdlite/recompose/blob/master/src/packages/recompose/utils/pick.js
|
||||
|
||||
const pick = (obj, keys) => {
|
||||
const result = {};
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export default pick;
|
||||
28
develop/utils/props2Stream.js
Normal file
28
develop/utils/props2Stream.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { Component } from 'react';
|
||||
import createEagerFactory from './createEagerFactory';
|
||||
import createHelper from 'recompose/createHelper';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import omit from './omit';
|
||||
|
||||
const prop2Stream = (propName, comparator = (a, b) => a === b) =>
|
||||
BaseComponent => {
|
||||
const factory = createEagerFactory(BaseComponent);
|
||||
return class extends Component {
|
||||
props$ = (new BehaviorSubject(this.props[propName]))
|
||||
.distinctUntilChanged(comparator);
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.props$.next(nextProps[propName]);
|
||||
}
|
||||
|
||||
render() {
|
||||
return factory({
|
||||
...omit(this.props, [propName]),
|
||||
[`${propName}$`]: this.props$,
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default createHelper(prop2Stream, 'prop2Stream');
|
||||
31
develop/utils/stream2Props.js
Normal file
31
develop/utils/stream2Props.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { Component } from 'react';
|
||||
import createEagerFactory from './createEagerFactory';
|
||||
import createHelper from 'recompose/createHelper';
|
||||
|
||||
// if stream prop will change this will fail,
|
||||
// this is expected behavior
|
||||
const stream2Props = (props2Stream) =>
|
||||
BaseComponent => {
|
||||
const factory = createEagerFactory(BaseComponent);
|
||||
return class extends Component {
|
||||
state = {};
|
||||
|
||||
componentWillMount() {
|
||||
this.subscription = props2Stream(this.props)
|
||||
.subscribe(value => this.setState({ value }));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
return factory({
|
||||
...this.props,
|
||||
...this.state.value,
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default createHelper(stream2Props, 'stream2Props');
|
||||
@ -69,7 +69,7 @@
|
||||
"file-loader": "^0.8.5",
|
||||
"jsdom": "^6.5.1",
|
||||
"kotatsu": "^0.14.0",
|
||||
"lodash": "^4.6.1",
|
||||
"lodash": "^4.13.1",
|
||||
"mocha": "^2.3.3",
|
||||
"node-sass": "^3.7.0",
|
||||
"normalize.css": "^4.1.1",
|
||||
@ -82,6 +82,7 @@
|
||||
"recompose": "^0.19.0",
|
||||
"reselect": "^2.5.1",
|
||||
"rimraf": "^2.4.3",
|
||||
"rxjs": "^5.0.0-beta.8",
|
||||
"sass-loader": "^3.2.0",
|
||||
"style-loader": "^0.13.1",
|
||||
"url-loader": "^0.5.7",
|
||||
|
||||
@ -435,6 +435,7 @@ export default class GoogleMap extends Component {
|
||||
ReactDOM.render(
|
||||
(
|
||||
<GoogleMapMarkers
|
||||
experimental={this_.props.experimental}
|
||||
onChildClick={this_._onChildClick}
|
||||
onChildMouseDown={this_._onChildMouseDown}
|
||||
onChildMouseEnter={this_._onChildMouseEnter}
|
||||
@ -873,6 +874,7 @@ export default class GoogleMap extends Component {
|
||||
const mapMarkerPrerender = !this.state.overlayCreated
|
||||
? (
|
||||
<GoogleMapMarkersPrerender
|
||||
experimental={this.props.experimental}
|
||||
onChildClick={this._onChildClick}
|
||||
onChildMouseDown={this._onChildMouseDown}
|
||||
onChildMouseEnter={this._onChildMouseEnter}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import shallowEqual from 'fbjs/lib/shallowEqual';
|
||||
import omit from './utils/omit';
|
||||
|
||||
const mainStyle = {
|
||||
width: '100%',
|
||||
@ -56,6 +57,14 @@ export default class GoogleMapMarkers extends Component {
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (this.props.experimental === true) {
|
||||
return !shallowEqual(this.props, nextProps) ||
|
||||
!shallowEqual(
|
||||
omit(this.state, ['hoverKey']),
|
||||
omit(nextState, ['hoverKey'])
|
||||
);
|
||||
}
|
||||
|
||||
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
|
||||
}
|
||||
|
||||
@ -79,7 +88,6 @@ export default class GoogleMapMarkers extends Component {
|
||||
}
|
||||
|
||||
const prevChildCount = (this.state.children || []).length;
|
||||
|
||||
const state = this._getState();
|
||||
|
||||
this.setState(
|
||||
@ -174,6 +182,13 @@ export default class GoogleMapMarkers extends Component {
|
||||
const hoverDistance = this.props.getHoverDistance();
|
||||
|
||||
React.Children.forEach(this.state.children, (child, childIndex) => {
|
||||
// layers
|
||||
if (child.props.latLng === undefined &&
|
||||
child.props.lat === undefined &&
|
||||
child.props.lng === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const childKey = child.key !== undefined && child.key !== null ? child.key : childIndex;
|
||||
const dist = this.props.distanceToMouse(this.dimesionsCache_[childKey], mp, child.props);
|
||||
if (dist < hoverDistance) {
|
||||
@ -211,14 +226,26 @@ export default class GoogleMapMarkers extends Component {
|
||||
|
||||
render() {
|
||||
const mainElementStyle = this.props.style || mainStyle;
|
||||
|
||||
this.dimesionsCache_ = {};
|
||||
|
||||
const markers = React.Children.map(this.state.children, (child, childIndex) => {
|
||||
const pt = this.props.geoService.project({
|
||||
lat: child.props.lat,
|
||||
lng: child.props.lng,
|
||||
}, this.props.projectFromLeftTop);
|
||||
if (child.props.latLng === undefined &&
|
||||
child.props.lat === undefined &&
|
||||
child.props.lng === undefined) {
|
||||
return (
|
||||
React.cloneElement(child, {
|
||||
$geoService: this.props.geoService,
|
||||
$onMouseAllow: this._onMouseAllow,
|
||||
$prerender: this.props.prerender,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const latLng = child.props.latLng !== undefined
|
||||
? child.props.latLng
|
||||
: { lat: child.props.lat, lng: child.props.lng };
|
||||
|
||||
const pt = this.props.geoService.project(latLng, this.props.projectFromLeftTop);
|
||||
|
||||
const stylePtPos = {
|
||||
left: pt.x,
|
||||
@ -238,11 +265,11 @@ export default class GoogleMapMarkers extends Component {
|
||||
// to prevent rerender on child element i need to pass
|
||||
// const params $getDimensions and $dimensionKey instead of dimension object
|
||||
const childKey = child.key !== undefined && child.key !== null ? child.key : childIndex;
|
||||
|
||||
this.dimesionsCache_[childKey] = {
|
||||
x: pt.x + dx,
|
||||
y: pt.y + dy,
|
||||
lat: child.props.lat,
|
||||
lng: child.props.lng,
|
||||
...latLng,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user