React(Redux)アプリで、FriendlyForwarding機能(ページ遷移戻し)を実装する。

Reduxのプロジェクトでフレンドリーフォワーディング機能を実装する機会があったので、
備忘録として記録しておきます。
今回は**cookieを使用した方法**になります。

#FriendlyForwordingとは
フレンドリーフォワーディングとか、かっこいい横文字並べがちなweb開発ですが、簡単に言えば、  
**ユーザーがログインした後、ログイン直前に閲覧していたページヘとリダイレクトさせる機能**
ですね。  
###具体的な事例
例えば、ログインしていないユーザーが  
```root/my_page/```  
というプロフィールページに行こうとした時に、当然ログインしてないわけですから、my_pageは表示できません。その際に一度ログインページに飛ばす必要があります。  
```root/sign_in```  
このページからログインした後に、  
```root/home```  
ではなくて  
```root/my_page```  
に飛ばしてあげましょうというものです。(わかりにくい)

ログイン後に一括的にユーザーをホームに飛ばすのではなく、各々の行きたいところに飛ばしてあげましょうっていう  
心優しい機能です。
昨今web開発に関しては、UI、UXのウェイトは高まってきているので、とても大切な機能の一つとなります。

##ディレクトリ構成
**※この記事内ではわかりやすいように直接関係ないものは全て削ってあります。**

```
redux  
 |     
 | -- container  
 |　　|  
 |　　| -- sign_in.js  
 |　　|  
 |　　| -- my_page.js  
 |　　|  
 |　　| -- home.js  
 |  
 | -- reducers  
 |　　|  
 |　　| -- root_reducer 
 |   
 |_ root.js
```

###root_reducerについて
これはreduxのルーティングプラグイン、「react-router-redux」です。今回は省略します。以下を参照してください。  
[GitHub : react-router-redux](https://github.com/reactjs/react-router-redux)

##ルーティング
以下 ``root.js``

```javascript
'use strict';

import React, {Component} from 'react';
import {Provider} from 'react-redux';
import {Router, Route, browserHistory} from 'react-router';
import {syncHistoryWithStore} from 'react-router-redux';
import rootReducer from './reducers/root_reducer';
import {SignIn, MyPage, Home } from './containers';

export default class Root extends Component {
  handleUpdate() {
    let {action} = this.state.location;

    if (action === 'PUSH') {
      window.scrollTo(0, 0);
    }
  }

  render() {
    const store = createStore(rootReducer);
    const history = syncHistoryWithStore(browserHistory, store);

    return (
        <Provider store={store}>
          <Router history={history} onUpdate={this.handleUpdate}>
            <Route path='/' component={App}>
              <Route path='home' component={Home}/>
              <Route path='sign_in' component={SignIn}/>
              <Route path='my_page' component={MyPage}/>
              <Route path='*' component={NoMatch}/>
            </Route>
          </Router>
        </Provider>
    );
  }
}

```

##実装したいこと
- 通常ログイン後は ``/home`` に遷移させる。
- ログインせずに ``/my_page`` にアクセスしようとしたユーザーを ``/sign_in`` に遷移させる。
- ログイン後に ``/my_page`` に遷移させる。

##実装方法
今回はcookieに直前のページURLを保存しておき、ログイン後にそのURLにリダイレクトさせます。  
はじめてcookieを触ることになったので、その辺も含めていい勉強になりました。

##ソースコード
###my_page.js
```javascript:my_page.js
'use strict';

import React, {Component} from 'react';
import {browserHistory} from 'react-router';
import {UserAction} from '../actions';

class MyPage extends Component {
  constructor(props) {
    super(props);
    this.state = {currentUser: null}
    if (!this.props.currentUser) {return}
    UserAction.getUser((user) => {
      if (this.state.isMounted) {
        this.setState({currentUser: user});
      }
    });
  }

  componentWillMount() {
    if (!this.props.currentUser) {
　　//①クッキーに現在のルート(/my_page)を、変数forwarding_urlをキーとして格納。
      document.cookie = `forwarding_url=${location.pathname}; path=/;`;
      //②sign_inに遷移。
      return browserHistory.push('sign_in');
    }
  }

  render() {
    return (
      <div>
        マイページだよ。
      </div>
    );
  }
}

export default MyPage;
```

###sign_in.js
```javascript:sign_in.js
'use strict';

import React, {Component} from 'react';
import {reduxForm} from 'redux-form';
import {browserHistory} from 'react-router';

class SignIn extends Component {
  //③ログインボタンを押した瞬間にcookieを読み込み、値があれば遷移させる。
  goRoot() {
    const searchForwardingUrl = new RegExp('forwarding_url=(.*?)(?:;|$)');
    if (document.cookie.match(searchForwardingUrl)) {
      return browserHistory.push(RegExp.$1);
    }
    //④もしなければ通常通りホーム画面に
    browserHistory.push('/home');
  }
  //⑤sign_in を離れる際にクッキーを削除（誤作動の防止）
  componentWillUnmount() {
    const pastDate = new Date(SignInConstants.PAST_DATE);
    document.cookie = 'forwarding_url=;path=/;expires=' + pastDate.toGMTString();
  }

  render() {
    return (
      <form>
        <div>
          <button onClick={this.goRoot}>ログイン</button>
        </div>
      </form>
    );
  }
}

export default SignIn;
```

###home.js
```javascript:home.js
'use strict';

import React, {Component} from 'react';
import {reduxForm} from 'redux-form';

class Home extends Component {
  render() {
    return (
        <div>Home</div>
    );
  }
}

export default Home;
```

##実装手順
おおきな処理は５つです
###⓪、ルーティングをする
今回は、react-router-reduxを使っているので仕様通りにルーティング。  
条件としては``my_page.js``に遷移した際にcurrentUserの値を

```javascript
 UserAction.getUser((user) => {
      if (this.state.isMounted) {
        this.setState({currentUser: user});
      }
```

という部分でデータベースから引っ張ってきます。
現在ログインをしているのであれば、stateにuserが入ります。


その後、``componentWillMount()`` 関数で、stateにcurrentUserが入っているかどうかを判断します。
``componentWillMount()``は、そのコンポーネントがレンダリングされる前、つまりviewの表示前に、自動的してくれる処理になります。

その中で

```javascript
  componentWillMount() {
    if (!this.props.currentUser) {
      //①クッキーに現在のルート(/my_page)を、変数forwarding_urlをキーとして格納。
      document.cookie = `forwarding_url=${location.pathname}; path=/;`;
      //②sign_inに遷移。
      return browserHistory.push('/sign_in');
    }
  }
```

②の処理にて、currentUserが入っているかどうかで、もし入っていなければ（未ログインであれば）homeに遷移させます。

###①クッキーをセットする
[cookieとは](https://ja.wikipedia.org/wiki/HTTP_cookie)
webを支える技術を最近読んだので、タイムリーでした。
みんなが聞いたことがある、けどなんだかよくわからんあれです。笑

cookieは、連想配列の形式になっていて、キーと値をセットで格納しています。
ちなみに確認したければ、コンソールから確認できますよ。
[chromeにおけるcookieの確認](http://www.buildinsider.net/web/chromedevtools/01)

上の①をもう一度。  
```document.cookie = `forwarding_url=${location.pathname}; path=/;`;```  
今回は**forwarding_url**というキーに現在いるpath ( my_page )を格納しています。  
別のキーでpathというのは、後で説明します。  

兎にも角にも、これでcookieにはforwarding_url、つまり見ていたページのurl(path)が保存されました。  


###②sign_inへの遷移
ソースコードを参照ください

###③cookieの読み込み
ログインボタンをおすと``goRoot()``が起動します。

``const searchForwardingUrl = new RegExp('forwarding_url=(.*?)(?:;|$)');``  
ここではsearchForwardingUrlという変数をconstで定義しています。  
constとはES6から出た新しい宣言方法です。  
おそらくこれから大切になってくるので、let,varとの違いは押さえておいたほうがいいでしょう。  
[ES6の新機能: 「let」「const」宣言を調べてみた](http://qiita.com/niisan-tokyo/items/bacb5ed98ea10831eeba)  

後半のnew以降は、正規表現を使って新しい検索ワードを生成しています。    
[正規表現の生成](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions)  
難しく感じるかもしれませんが、これは要するに、cookieにforwarding_urlという    
値を持ったプロパティが設定されているか、という意味の検索になります。  
**forwarding_urlがない　→　他のページからは来ていない　→　homeに遷移**  
というロジックです。  

```javascript
   if (document.cookie.match(searchForwardingUrl)) {
      return browserHistory.push(RegExp.$1);
    }
```
続いてはcookieというオブジェクトにmatchというメソッドを適用します。  
なんだか難しそうですが、  
**"searchForwardingUrl'という検索ワードで、cookieの中を検索しているだけ ** 
です。  
検索した値があるのであれば、（friendly_forwardingが存在すれば)  
return以下の処理をします。  
``RegExp.$1``は、検索した値の中での、１番目の値が入っています。  
このようにする理由は、cookieの中には様々なデータが記憶されており、  
その中での順序を操作できないからです。  

###④もしなければ、/home に遷移
もし``friendly_forwarding``が存在しなければ、  
/homeに遷移します。  

###⑤sign_in を離れる際にクッキーを削除（誤作動の防止）  
こちらがポイントかなと思うのですが、  
cookieというのは意図的に削除しない限りブラウザにずっと記憶されてしまいます。  
今回の例で言うと、もしずっと``/my_page``というように記憶されてしまっていては、    
普通にログインしたのに毎回``/my_page``に飛ばされてしまう、、、  
ということが起きてしまいます。  
あくまで**forwarding_url** つまり、直前のurlであって、  
一時的である必要があります。  
そのため、``/sign_in``を離れる瞬間に、必ず消すようにしたいです。  

消し方は、少し変な感じがしますが、  
**cookieの有効期限を現在より前にする。**  
という方法で消します。

```javascript
  componentWillUnmount() {
    const pastDate = new Date('1970/01/01');
    document.cookie = 'forwarding_url=;path=/;expires=' + pastDate.toGMTString();
  }
```

``componentWillUnmount()``は、そのコンポーネントがレンダリングされた後、    
つまりviewの表示終了直前に、自動的してくれる処理になります。  

pastDateという変数に過去の日時  
（今回は1970年1/1から、この時間は計測が開始される基準になっています。）    
を入れます。  

※1970/01/01などは、特に意味を持っていないので、可読性が低いです。  
　**このようなケースでは``PAST_DATE:'1970/01/01'``などと設定し  その変数名を使うのが一般的です。**   
　　このように変数を使うことでコードの可読性と再利用性を高めるために使うものを  
　　**マジックナンバー**と言います。    

cookieの中のexpiresとは、その値の**有効期限**を表します。  
これを過去に設定することで、その値を削除するのです。  
う〜ん不便。  
正直バットプラクティスな気がしてなりません。  


##まとめ
兎にも角にも、cookieでの実装はそこまで難しくはないと感じました。  
しかし反面で、cookieはユーザー側でいじることができたり、  
あまり機能しないことが多い（らしい）です。  
加えて、セキュリティ面でも悪いことがあるそうです。（ざっくり）  

もっといい実装方法があれば、更新したいと思います。