i18Next-React 造成 Component 不斷重複Render 問題

最近剛好在研究i18Next, 遇到了一個小坑, 特此紀錄。

首先新增了一隻 i18n.js 提供給Component使用:

import i18next from 'i18next';
import XHR from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';


i18next
.use(XHR)
.use(LanguageDetector)
.init({
fallbackLng: ["en","zh-TW"],
ns: ['common'],
defaultNS: 'common',
debug: false,
interpolation: {
escapeValue: false
},
load: 'currentOnly',
backend: {
"loadPath": "/public/locales/{{lng}}/{{ns}}.json"
}
});
export default i18next

利用Provider 注入i18n

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { I18nextProvider } from 'react-i18next';
import i18n from 'i18n/i18n.js';
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import RouterMain from './router/RouterMain';
import App from './App'
import 'css/main.sass';

const createStoreWithMiddleware = applyMiddleware()(createStore);

ReactDOM.render(
  <I18nextProvider i18n={ i18n }>
    <App/>
  </I18nextProvider>
  ,
  document.getElementById('root')
);
 

在我的元件中使用 translate 函數, 會發現render函數持續被呼叫。因此我在componentWillReceiveProps中觀察props的變化,發現props中的i18n有個tReady屬性會一直被watch , 因此我自己新增一個stopDetectTranslateReady函式來處理,或許有更好的處理方法,不過我只是為了測試i18n,因此沒有特別修改,如果有更好的解法也請大家不吝指教。

import React, { Component } from 'react';
import { Link } from 'react-router-dom'
import { translate } from 'react-i18next';
import {stopDetectTranslateReady} from 'helpers'

class NavBar extends Component {
  constructor(props){
    super(props);
  }

  shouldComponentUpdate(nextProps){
    return stopDetectTranslateReady(nextProps);
  }

  componentWillReceiveProps(props){
    console.log('componentWillReceiveProps', props)
  }
  render() {
    
    const { t } = this.props;    
    return ( 
      <nav>
        <div className="flex flex1 flex-center">
          <div className="flex flex1"></div>
          <div className="flex flex5 flex-center">
            <h2 style={{fontWeighjt: '300'}}>{t('appName')}</h2>
          </div>
          <div className="flex flex1 flex-right">
            <i className="fas fa-bars font-lg" style={{marginRight: '10px'}}></i>
          </div>
        </div>
        <div className="flex flex4 menu-list">     
          <Link to="/reservation" className="menu-link">
            <i className="far fa-check-circle"></i>
            <span>{t('navbar.reservation')}</span>
          </Link>
          <Link to="/favorite" className="menu-link">
            <i className="far fa-heart"></i>
            <span>{t('navbar.mycamp')}</span>
          </Link>
          <Link to="/" className="menu-link">
            <i className="far fa-thumbs-up"></i>
            <span>{t('navbar.famous')}</span>
          </Link>
          <Link to="/" className="menu-link">
            <i className="far fa-snowflake"></i>
            <span>{t('navbar.equipment')}</span>
          </Link>
          <Link to="/" className="menu-link">
            <i className="fas fa-user-astronaut"></i>
            <span>{t('navbar.about')}</span>
          </Link>
        </div>
       
      </nav>
    )
  }

}


const mobileMenuContentStyle = {
  justifyContent: 'flex-end'
}

export default translate()(NavBar);

stopDetectTranslateReady函式:


export const stopDetectTranslateReady =  nextProps => {
    if(nextProps.tReady){
      return true;
    }
    return false;
}

JWT JSON Web Token 驗證簡述

JSON Web Token, 或JWT (jot) 簡單來說, 是一種傳送驗證授權的標準。 為什麼我們需要JWT? 從前我們在驗證使用者身份後,可能會將使用者資訊存在於session當中,這種做法有幾種缺點:

  • 造成Server記憶體負擔
  • 如果需將Application掛載於多台Server上,必須要同步所有的Server的session
  • 無法處跨平台授權問題

於是,JWT誕生了。因為Http 的請求是無狀態的(stateless)因此我們必須將sessiont儲存在Server Side. 我們利用JWT的技術標準,由Server Side產生一組token給予Client Side, 再由Client Side保存好,並在每一次Request將token帶入。 這間接解決了cookie的缺點,不易被竄改。

談到這我們就可以來說明token的優點:

安全性

如上述所說明, 除了token沒機會被修改以外, 我們也可以要求token的時效性來提高安全性。

擴充性

我們可以利用token來分享權限給予第三方使用者

解決跨域問題

傳統cookie在登入的時候必須在所有domain寫入,但token只要實作相同的驗證機制即可


說了這麼多,我們還是要來談論,如何實作JWT機制

JWT主要分為三個部分:

  • header (標頭)
  • payload(訊息內容)
  • signature(簽章)

最終組成一個Token:

{header}.{payload}.{signature}

 

Header的內容為 JSON物件:

{
  "alg": "HS256",
  "typ": "JWT"
}

Header 主要是說明JWT將用何種演算法建立。

 

Payload的內容也是JSON物件:

{
 "sub": "1234567890", 
 "name": "John Doe", 
 "admin": true
}

Payload主要承載使用者需要的資訊,官方的HandBook有說明沒有硬性規定哪些必要屬性。
但仍舊可以歸納出幾個特別的要求:

  • iss:    issuer,帶有機密資料的字串或是URI,可明確指出是由誰發行此JWT
  • sub:  subject,帶有機密資料的字串或是URI,說明JWT的內容
  • aud: audience,帶有機密資料的字串或是URI或是陣列,指出JWT的受眾
  • exp: expiration,代表特定時間的數字,依循POSIX定義的"seconds since epoch"格式,明確指出JWT的過期時間
  • nbf: not before,與exp相反,依循POSIX定義的"seconds since epoch"格式,明確指出JWT的的開始生效日期
  • iat: issue at (time),與exp和nbf格式相同,為JWT的發行時間
  • jti: JWT id,識別此JWT的唯一字串

 

JSON Web Signatures 大概是JWT 最重要的特性之一,先前說過JWT是由Header.Payload.Signature組成,
大概會是類似下面的範例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. (header)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.(payload)
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ(signature)

依據JWT 規範,有數種簽章演算法可用,因此會有多種不同的解譯。 JWT要求單一演算法以支援相同實作

  • HMAC using SHA-256,在JWT中稱作HS256
  • RSASSA PKCS1 v1.5 using SHA-256,在JWT稱作RS256
  • ECDSA using P-256 and SHA-256,在JWT稱作ES256

JWA為JSON Web Algorithms的縮寫,還有許多演算法就不在此詳列。

HMAC-based的簽章演算法範例如下:

const encodedHeader = base64(utf8(JSON.stringify(header)));
const encodedPayload = base64(utf8(JSON.stringify(payload))); 
const signature = base64(hmac(`${encodedHeader}.${encodedPayload}`,secret, sha256));
const jwt = `${encodedHeader}.${encodedPayload}.${signature}`;

 獲得jwt後,即可傳送至Client Side保存,每次發送request即帶上該token。  Server Side可以用middle ware解析並驗證token。 如此一來即可輕鬆確認使用者身份,亦可減輕Server負擔。 當然實務上的實作還有很多細節要交代,在此只是做為簡述,想了解可進一步至 https://jwt.io/ 的網站下載HandBook來詳讀。

Binary Search in Javascript

    const binarySearch = (arr, target) => {
     // arr 必須排序,  否則要實作 arr.sort( (a,b) => a - b);
     if(!arr || arr.length === 0){
        return -1;
     }
      const len = arr.length;
      let start =  0, end = arr.length -1;
      while(start + 1 < end){
          let mid = start + parseInt((end - start)/2); //找出中位數
          if (arr[mid] === target) {
            return mid;
          }
          else if (arr[mid] < target){
            start = mid;
          }
          else{
            end = mid;
          }  
      }

      if(arr[start] === target) {
        return start;
      }
  
      if(arr[end] === target) {
        return end;
      }
  
      return -1;
    }

    (function(){
      let result = binarySearch([1,2,3,4,5],5);
      console.log(result) //4
    })()

解題練習 – subsets

最近開始練習解題,決定將每次的練習記錄下來
有關subsets的處理幾乎都是以深度優先為第一考量
解題的方式不外乎:

  1. 排序
  2. 刪除重複
  3. 遞迴

由於subsets是tree的概念, 我們會向下搜尋,
因此必須以pos作為一個基準點, 每次遞迴就往下層尋找。
每次的遞迴把我們搜尋到的 subset 存入我們的result陣列。
list則是提供一個暫存的容器。當我們搜尋完某個支線後,
要爬回上一層節點,所以必須remove(splice)最後一個元素。

以下是javascript的程式碼,僅供參考,如有錯誤還請不吝指正。

const subsetHandler = function(result, list, nums, pos){
    result.push([].concat(list)); //將子集放入result
    for(let i = pos; i < nums.length; i ++){
        list.push(nums[i]); //子集的容器
        subsetHandler(result,list,nums,i+1);
        list.splice(list.length-1,1); //刪除最後的元素
    }
};
const subsets = function(nums) {
    const len = nums.length;
    let result = [];
    let list = [];
    nums = nums.sort((a,b) => a - b); //先排序
    subsetHandler(result,list,nums,0);
    return result;
};

Webpack Uglify.js 不支援ES6語法

記錄一下前幾天踩的雷:

在terminal使用webpack指令做打包動作一切正常,但使用webpack -p做打包出現了Unexpected token (>) 的錯誤問題

google了一下發現原來是unglifyjs不支援es6語法,因此我寫的arrow function造成壓縮失敗。 只要安裝babel-preset-env

yarn add babel-preset-env --save -dev

並在.babelrc中設定:

{
"presets": ["env"]
}

重新執行webpack -p之後即可正常打包。

淺談React with Redux (1) – Redux 概念

由於筆者沒使用過Flux, 所以在這邊不會特別介紹Flux,也不會做相關的比較,在此先跟大家道歉。

為什麼要用Redux?

這個問題很簡單,親手開發一個單純的React專案。 當我們的組件越來越多,包的子組件越來越多時,我們很有可能會遭遇到state或props難以管理的問題。 這裡我們就要借助一個名為"Redux"的Pattern來簡化這些複雜的工作。 也因此,我們Redux不是只能應用在React身上。

Redux的三大定律

  1. 單一資料流
  2. state只能讀取
  3. 使用純函數做資料修改

單一資料流–  所有的state皆儲存於一個JavaScript的物件中

state只能讀取–  我們不能修改state的資料,修改資料唯一的方式就是觸發一個action

純函數做資料修改–   我們利用reducer來處理state與action,reducer就是一個很單純的function

看到這裡, Redux的初學者應該還是不懂,到底什麼是action , 什麼是reducer。先來看看圖表吧。

圖表來源

 

我們先來談談Action, 到底什麼是Action呢?根據React Redux的官網描述:
Actions are plain JavaScript objects. Actions must have a type property that indicates the type of action being performed.

亦即Action是一個純js物件,物件必須帶有一個屬性叫做type,舉例來說:

{
type: 'Select_Book',
payload: 'Harry Potter'
}

就是一個Action, payload就是由外部(例如: UI 觸發)傳入的新state。
可是單純一個物件並沒有任何作用,因此我們需要一個ActionCreator作為窗口,用來建立Action物件。
所以我們可以定義一個函數叫做:

function selectBook(bookName){
   return {
     type: 'Select_Book',
     payload:  { selectedBook: bookName }
   };
}

現在我們可以來談談Reducer了,Redux的命名也是由Reducer來的。 Action定義了要執行的行為,但並未描述state的變化,因此我們要借助reducer來處理state。 在Redux中,我們會把所有的資料儲存在唯一的物件中,我們當然可以只寫一個reducer來處理所有的action,但隨著專案規模越來越大的時候,reducer會變得相當擁腫,這時我們就需要拆分數個reducer來降低複雜度。

首先我們會創建一個物件來初始化state

const initialState = [];

reducer事實上也是一個純函數,所以我們可以這樣寫:

 function bookReducer(state = initialState, action){
    switch(action.type){
      case: 'Select_Book':
      return Object.assign({}, state, {
        selectedBook: action.payload.selectedBook,        
      });
    }
    return state;
 }

 

reducer主要接收兩個參數,一個是action,另一個是state。 它的工作就是根據action的定義來修改state。
回傳使用Object.assign這個方法是因為我們要確保每次的變化都是獨立的。
說到這邊,讀者應該已經大致瞭解action,reducer的作用了。
至於action要如何跟reducer結合,這我們留待下次再討論吧。

 

 

p.s 由於筆者是前端初心者, 本身為後端開發人員, 我盡量以新手的觀點來撰寫本文以求每個人都可以輕易上手。
如有問題請提出, 有錯誤希望各位高手不吝指教,謝謝。

webpack 配置學習筆記

webpack 帶來的好處在此先不贅述, 我只針對文件配置作解釋,如有錯誤還請各位大大指正。

  • context: 根路徑(絕對路徑)
  • entry:  bundle的文件入口, 可傳入字串,陣列與物件
  • output:  輸出結果描述,協助webpack編譯實體檔案, 其中包含- path: 輸出目錄(使用絕對路徑)
    – filename: 輸出文件名稱(不可使用絕對路徑)
    – publicPath: 輸出目錄的對應路徑 (亦即瀏覽器的訪問路徑)
  • modules: 可以定義loaders , 例如babel-loader來協助我們轉譯ES2015語法

以下是一個簡單的webpack.config.js範例(官網):

{
  context: __dirname + "/app",
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/build'
  },

  loaders: [{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader'
  }]
}

publicPath 指的是最終瀏覽器的訪問路徑,因此如果我們bundle後的結果最終須輸出於CDN
那麼我們的publicPath就要指向該CDN網址。

如果只定義了babel-loader,那當然還不夠用,我們還是要安裝相依性套件:

npm install --save-dev babel-core babel-preset-es2015
npm install --save-dev babel-loader

安裝好套件後,我們必須在.babelrc 的文件中加上以下設定:
 { “presets“: [ “es2015″ ] }
  這是要告訴babel說,我們使用的是es2015語法。

以上大概就是簡單的webpack配置,我將會定期更新此文件,以期待自己能夠更深入了解Webpack

建置React-Redux開發環境

以下假設您已經安裝好npm or yarn等套件

1. 安裝全域套件create-react-app

npm install -g create-react-app

2. 接下來我們可以用該套件來建立我們的模板

create-react-app practice_01

3. 安裝完成後進入該資料夾

cd practice_01

4. 接著安裝相依性套件
npm install

5. 再來安裝redux套件

npm install redux
npm install react-redux

大功告成!

備註:

create-react-app 本身已經安裝了webpack , 若您需要做細部的config設定

可以執行 npm run eject ,但執行此命令後,就無法再回復原本乾淨的開發環境囉!