好久不見,在 資服創新競賽 結束後,我耍廢了好一段時間,更準確來說,從校內專題比賽結束後就開始耍廢了 XD,Hexo 的開發停滯這麼久真是不好意思 てへぺろ(・ω<)。
我在暑假時開始進入 Dcard,利用 React + Redux + React Router 寫了一個和現行網站完全分離的行動版網站,那時候 Redux 的文件還非常不齊全,很多東西都得自己摸索,不過拜 @tomchentw 所賜,解決了很多架構上的問題。
然而在我跑去寫 V2 API 的這兩個月,Redux 竟然從 1.0.0 升級到 3.0.4 了!React Router 也終於推出 1.0.0 了!原本以為新版 Admin panel 也能沿用相同的配置,沒想到還是有一些部分得推倒重來,不禁令人感嘆前端變化之快。
Redux
先說說 Redux 究竟有什麼差別吧,其實 Redux 本身的修改倒還好,只要看 Changelog 就 OK 的程度,但是它周邊的東西就麻煩了。react-redux 從 0.2.1 跳到 4.0.0,此外還多了一堆伙伴,Redux 生態圈的形成也他媽的太快了吧!
react-redux
react-redux 本身的 API 很簡單,但是 Changelog 卻能出現好幾次的 Breaking Change,這東西是有這麼容易 Break 逆!
主要的差別在於 connect
function 變得更好用了,過去只能用來綁定 props,現在也能綁定 action,如此一來就能省去 bindActionCreators
的過程了。
@connect(state => ({
// props
}), {
// actions
})
另一點則是 Provider
class,原本的寫法實在不怎麼優雅,this.props.children
傳入的是一個函數,而現在可以直接傳入 React element。
React.render(
<Provider store={store}>
<App/>
</Provider>
, document.getElementById('root'));
redux-router
redux-router 是用來整合 Redux 和 React Router 的,他做的事情很簡單,就是把 Router 的資料存在 Redux store 裡,但是實際上用起來卻有不少問題,所以我先說結論:
改用 redux-simple-router,不然就不要把 Router 狀態存在 Redux store 裡。
你一定會很好奇我怎麼會去推薦一個版號連 0.1 都不到的超新星套件,但是 redux-router 這貨真的很坑,為了你的肝指數著想,請等到 1.0.0 正式版推出後再考慮它吧。
說了這麼多,這東西究竟有什麼問題呢?咱們來一一列舉吧:
- redux-router 和 redux-simple-router 最大的不同就在於:前者把完整的 Router 狀態都存到了 Redux store,而後者只存了路徑,在 Dehydration/Rehydration 時,根本沒辦法處理 redux-router 的資料,只能忽略。
- redux-router 的觸發時機早於 Rehydration,因此有些資料尚未初始化而發生錯誤。
- redux-router 的
getRoutes
參數半壞,你如果想把 Redux store 傳進 routes 裡的話,自己實作比較保險。 - redux-router 的原始碼不知道在複雜幾點的,而 redux-simple-router 的原始碼不到 100 行,雖然功能比較少,但是好用多了。
接著比較實際的程式碼吧:
import React from 'react';
import {render} from 'react-dom';
import {createStore, compose} from 'redux';
import {ReduxRouter, reduxReactRouter} from 'redux-router';
import {createHistory} from 'history';
const store = compose(
middlewares,
reduxReactRouter({
routes,
createHistory
})
)(createStore)(rootReducer, initialState);
render(
<Provider store={store}>
<ReduxRouter/>
</Provider>
, document.getElementById('root'));
import React from 'react';
import {render} from 'react-dom';
import {createStore, compose} from 'redux';
import {createHistory} from 'history';
import {syncReduxAndRouter} from 'redux-simple-router';
import {Router} from 'react-router';
const history = createHistory();
const store = compose(
middlewares
)(createStore)(rootReducer, initialState);
syncReduxAndRouter(history, store);
render(
<Provider store={store}>
<Router routes={routes} history={history}/>
</Provider>
, document.getElementById('root'));
redux-router 需要在 createStore 時就加入 reduxReactRouter
middleware,而 redux-simple-router 則是在 store 創建完成後才同步 router 狀態;redux-router render 時使用 ReduxRouter
元件,而 redux-simple-router 直接使用 React Router 的 Router
元件。由於後者非常簡單,也減少了錯誤發生的機率。
redux-devtools
這東西真的很炫,它可以在頁面中顯示側欄來顯示 action 的動態,還可以復原某些 action 對 store 的變更,使用起來非常簡單,讓我愛不釋手。
import React from 'react';
import {render} from 'react-dom';
import {createStore, compose} from 'redux';
import {persistState} from 'redux-devtools';
import {createDevTools} from 'redux-devtools';
import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';
const DevTools = createDevTools(
<DockMonitor
toggleVisibilityKey='H'
changePositionKey='Q'>
<LogMonitor/>
</DockMonitor>
);
const store = compose(
DevTools.instrument(),
persistState(
window.location.href.match(
/[?&]debug_session=([^&]+)\b/
)
)
)(createStore)(rootReducer, initialState);
render(
<Provider store={store}>
<DevTools/>
</Provider>
, document.getElementById('root'));
React Router
React Router 從 0.13 到 1.0 的差別非常大:
- Named route 被移除了,原本
Link
元件能從 named route 自動產生完整的路徑,現在必須自己來 - 不一定得用 JSX 來寫 route,現在也能用 plain object 了,這還挺方便的
willTransitionTo
=>onEnter
,willTransitionFrom
=>onLeave
- 支援 Async child routes,現在能更方便的切頁面了
我從暑假時因為 React context 的關係就開始用 React 0.14 + React Router 1.0 beta 了,所以轉換起來比較無痛,但是 1.0 beta 和正式版還是有些 API 差別的,因為一言難盡,還是看 Changelog 吧。
React Transform
原本我使用的是 React Hot Loader,這種方法其實用起來也沒什麼大問題,只是得另外開個 webpack-dev-server。不過原作者決定廢棄 React Hot Loader,又另外開了個 React Transform,所以我也只好轉移到 React Transform 了。相較上面幾個大改變來說,這只是編譯過程的改變而已,只需要改 Webpack 和 Babel 的配置即可。
首先把 webpack-dev-server 相關的東西都移除掉,安裝:
npm install babel-plugin-react-transform --save-dev
npm install react-transform-catch-errors --save-dev
npm install react-transform-hmr --save-dev
npm install redbox-react --save-dev
npm install webpack-dev-middleware --save-dev
npm install webpack-hot-middleware --save-dev
以下程式碼使用的是 Express,如果你用的是 Koa 的話,請把 webpack-dev-middleware
改成 koa-webpack-dev-middleware
,webpack-hot-middleware
改成 koa-webpack-hot-middleware
,這兩個 middleware 的使用方式會有些差異,不過大致上是差不多的。
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import webpackConfig from './config';
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath
}));
app.use(webpackHotMiddleware(compiler));
import webpack from 'webpack';
export default {
entry: {
main: [
'webpack-hot-middleware/client',
'./src/main'
]
},
module: {
loaders: [
{
test: /\.jsx?$/,
loaders: ['babel'],
exclude: /node_modules/,
query: {
plugins: ['react-transform'],
extra: {
'react-transform': [
{
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module']
},
{
transform: 'react-transform-catch-errors',
imports: ['react', 'redbox-react']
}
]
}
}
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin()
]
};
Babel
Babel 在上個月也從 5 升級到 6 了,但現在還有很多問題:
- 眾多功能拆成 Plugin 後,由於執行順序的問題,可能互相衝突,例如 Decorator 怪怪的
import *
的行為和過去不同,這有可能是上一點的問題- 最重要的是,React Transform 尚未支援
如果你現在 Babel 5 用得好好的,那就別折騰了。
後記
你可以在 tommy351/redux-example 看到完整的 Universal Redux example。
前端的變化這麼快實在太可怕了,明年說不定又有新玩意了,我還是回去寫後端壓壓驚吧。
題圖是本季動畫「緋弾のアリアAA」,我原本以為這是和本傳差不多的動畫,沒想到一看就讓我震驚了,花了兩天把原作漫畫讀完,在某些方面來說,這的確和本傳差不多:
- 主角「間宮明里」,和金次一樣有吸引後宮的體質,只是明里專門吸引サイコレズ,這世界就沒半個正常人吧。
- 明里在平常就是個低等廢物,但是危急的時候總是能用祖傳炫泡絕技打爆敵人來收後宮。
- 白雪(本傳)和志乃(AA)都非常喜歡主角,也都對亞莉亞抱有敵意www
目前動畫除了有些設定變更和大☆崩★壞的第五話以外,日常場景都挺不錯的,看來動畫工房的百合日常比較穩定。