iT邦幫忙

1

[筆記] React 如何使用 Redux-Observable 及 Google Map Api:副線打怪一下(3) - Finish

口罩即時查

先附上作品網址及成果展示:口罩即時查
https://ithelp.ithome.com.tw/upload/images/20200305/20123842MX2os0BFo8.png
https://ithelp.ithome.com.tw/upload/images/20200305/20123842wGKhHPQjjn.png

功能(電腦、平板、手機皆可觀看)

電腦版 ᴘᴄ
平板 ᴛ
手機版 ᴍ

  • 定位 - 在地圖上顯示目前位置 ᴘᴄ
  • 藥局顯示 - 在地圖上顯示藥局
    • 藥局列表 ᴛ、ᴍ (點選藥局可以快速前往目前位置) ᴘᴄ
    • 滑鼠滑過marker時顯示口罩剩餘數量 ᴘᴄ
    • 點擊marker時顯示藥局營業時間及備註 ᴘᴄ
  • 搜尋 - 亦可於搜尋框中搜尋欲查詢的藥局資訊(輸入藥局名稱、區域及地址皆可)ᴘᴄ、ᴛ、ᴍ
  • 回到現在位置鈕 - 點選地圖右側的藍色按鈕可以回到現在位置 ᴘᴄ
  • 撥打電話 - 點選按鈕可以快速聯繫藥局來詢問詳細資訊 ᴘᴄ、ᴛ、ᴍ

程式碼紀錄(Redux-Observable)

上篇文章已經可以成功抓取藥局資訊,這邊會著重於紀錄將藥局資訊顯示到畫面上的過程,那就繼續看下去吧!
發送請求的action

/* actions/index.js */

//發送 口罩資訊request
export const fetchMaskInformation = (callback) => {
    return {
        type: 'FETCH_MASK_INFORMATION',
        callback
    };
}

//接收口罩資訊
export const recieveMaskInformation = (res, updateTime) => {
    return {
        type: 'RECIEVE_MASK_INFORMATION',
        res: res,
        updateTime: updateTime,
    };
}

epic函數

/* epics/index.js */

const maskGetList = (action$) => 
    action$.ofType('FETCH_MASK_INFORMATION').pipe(
    mergeMap(({callback}) => { 
        return merge(
            //發送request時設定isLoading為true來告知使用者正在抓取資料
            of(actions.maskInformationIsLoading(true)),  
            ajax.getJSON(`網址`).pipe(
                mergeMap(response => {
                    // console.log(response.features);
                    // 傳入callback function
                    if(callback) callback(response.features);
                    // 更新時間
                    const updateTime = response.features[0].properties.updated; 
                    return of(
                        // fetch完之後再次發送action將資料存到reducer
                        actions.recieveMaskInformation(response.features, updateTime),
                        // 收到response後設定isLoading為false告知使用者抓取資料完成
                        actions.maskInformationIsLoading(false),
                    );
                })
            )
        )
    })
);

將fetch資料存到reducer調用

/* reducers/index.js */

const model = {
    maskInformationLists: [],
    updateTime: '',
};

const Reducer = (state = model, action) => {
    switch (action.type) {
        case 'RECIEVE_MASK_INFORMATION':
            return {
                ...state,
                maskInformationLists: action.res,
                updateTime: action.updateTime,
            }
            
        default:
            return state;
    }
};

export default Reducer;

接下來的部分就是Redux的使用啦/images/emoticon/emoticon01.gif,就不再另外紀錄Redux的部分。

程式碼紀錄(Google Map Api)

Google Map 使用的是itsmichaeldiego作者開發的google-map-react套件

安裝

npm install --save google-map-react

or

yarn add google-map-react

程式碼


...

class map extends Component {
    constructor() {
        super();
        this.state = {
            center: center,
            zoom: 16,
            features: [],
        };
    }
    
    ...

    // 取得目前位置
    getLocation = () => {
        if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition(this.updateLocation);
        }
    }

    // 取得目前位置後插上marker並且將現在位置設定為中心點
    updateLocation = (position) => {
        this.setState({
            center: {
                lat: position.coords.latitude,
                lng: position.coords.longitude,
            }
        }, () => {
            // console.log(this.map);
            // 定位完成後,刪掉起始位置的marker
            this.marker.setMap(null);
            // 新增現在位置的marker
            this.marker = new google.maps.Marker({
                position: new google.maps.LatLng(this.state.center.lat, this.state.center.lng),
                map: this.map,
                // 自訂icon圖示
                icon: location
            });
            this.map.setCenter(this.state.center);
            this.map.setZoom(16);  
        });
    }

   ...

    handleApiLoaded = (map) => {
        this.map = map;
         // 給定初始位置,如果使用者不允許抓取現在位置時則使用初始位置
        this.marker = new google.maps.Marker({
            position: new google.maps.LatLng(this.state.center.lat, this.state.center.lng),
            map: this.map,
            icon: location,
        });

        // 在Google Map上新增custom button
        // constructor passing in this DIV.
        var controlDiv = document.createElement('div');
        // Set CSS for the control border.
        var controlUI = document.createElement('div');
        controlUI.style.backgroundColor = '#fff';
        controlUI.style.border = '2px solid #fff';
        controlUI.style.borderRadius = '3px';
        controlUI.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)';
        controlUI.style.cursor = 'pointer';
        controlUI.style.marginRight = '10px';
        controlUI.style.textAlign = 'center';
        controlUI.title = 'Click to recenter the map';
        controlDiv.appendChild(controlUI);

        // Set CSS for the control interior.
        var controlText = document.createElement('div');
        controlText.style.color = 'rgb(25,25,25)';
        controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
        controlText.style.fontSize = '16px';
        controlText.style.lineHeight = '38px';
        controlText.style.paddingLeft = '5px';
        controlText.style.paddingRight = '5px';
        controlText.innerHTML = `<img src=${centerGPS} alt=""></img>`;
        controlUI.appendChild(controlText);

        // Setup the click event listeners: simply set the map to Chicago.
        controlUI.addEventListener('click', this.getLocation);
        this.map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(controlDiv);

        // ApiLoaded完之後才fetch
        this.props.fetchMaskInformation(this.createMarker);

        this.getLocation();

    };

    createMarker = () => {
        // 在此function插marker、建立infowindows動作、marker cluster
        
        ...
    }

    render() {
        const { classes } = this.props;
        return (
            <div className={classes.container}>
                <GoogleMapReact
                    bootstrapURLKeys={{ key: /* YOUR KEY HERE */ }}
                    defaultCenter={center}
                    defaultZoom={this.state.zoom}
                    yesIWantToUseGoogleMapApiInternals
                    onGoogleApiLoaded={({ map, maps }) => this.handleApiLoaded(map, maps)}
                >
                </GoogleMapReact>
            </div>
        );
    }
}

參考來源:
google-map-react
Maps JavaScript API

以上是這次口罩練習的紀錄,之後如果還有加上typescript的話會再補充上來。

心得

透過這次的口罩練習確實的把Redux及Observable好好的實作一次,從中更瞭解了從發送request到接收到response再到呈現到畫面上的整個流程之外,也在途中發生了一個小插曲,讓我體會到Observable的方便之處,也覺得自己的戰鬥力提升了不少XD
那就來分享個小插曲,前幾篇文有提到Observable的優點以及分享了Netflix發表的演講影片來敘述為什麼要使用Observable,當時只是知道說原來有這樣的優點,但還是抱持著真的有這麼好用嗎的想法,這次在處理藥局資訊的response時我就因為同時發送fetch以及Google map api產生了不同步行為,要是fetch先完成那沒問題,地圖會正常顯示並且插上marker,但若是因為網路問題或是其他因素導致fetch較慢完成,就會造成地圖先loaded完成,卻沒有插上各個藥局的marker,所以使用Observable就能輕鬆地解決這個問題,這就是Observable的強大啊!!

問題與討論

我也是前端的小菜鳥,所以要是有什麼寫得不好的地方,大師路過還請多給我一些指點,也請各位大師鞭小力點XD
如果你/妳已經是在前端上有一兩年經驗的,因本系列文章偏向前端入門,可能本系列文章不太適合你/妳,但也歡迎你/妳給予一些建議。


尚未有邦友留言

立即登入留言