ES6, React, Redux, Webpack寫的一個爬 GitHub 的網頁
0x01. 這是一個什么玩意兒?
github上有太多太多的牛人, 這個東西可以幫助你通過給定的一個github的用戶, 然后通過他關注的人, 找出他關注的人里的被關注數最高的幾個。 然后不斷的循環, 從而通過關系鏈找到github上最受歡迎的大神~ 這個東西還只是一個小東西, 如果你有興趣, 可以fork這個小的不能再小的項目...
項目截圖
0x02. 為什么要做這個東西?
一來是自己確實想做著玩一玩, 還有就是這個項目用到了react + redux. 想進一步的熟悉redux這個玩意兒。
0x03. 開工開工~ 搭建環境
用到的工具:webpack. 直接上配置
var webpack = require('webpack');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');
module.exports = {
entry: [
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080',
'./src/entry.js'
],
output: {
path: './build',
filename: '[name].js'
},
module: {
loaders: [
{ test: /.less$/, loader: 'style!css!less' },
{ test: /.jsx?$/, loader: 'babel-loader', exclude: /node_modules/ }
]
},
plugins: [
new OpenBrowserPlugin({ url: 'http://localhost:8080' }),
]
}</code></pre>
0x04. 使用 react-redux
當我剛開始使用react-redux的時候, 我的內心是絕望的. 什么connect, provider, 各種props映射, 各種dispatch映射.但是畢竟就是想拿這個東西順便學習一下react-redux. 話不多說, 上代碼!
entry.js
const loggerMiddleware = createLogger()
let store = createStore(reducer, compose(
applyMiddleware(thunk, loggerMiddleware),
window.devToolsExtension ? window.devToolsExtension() : f => f
))
devTools.updateStore()
render(
<Provider store={store}>
<App />
</Provider>, document.getElementsByTagName('div')[0])
if (module.hot) {
module.hot.accept()
}</code></pre>
這里用到了 redux中間件 的概念, 這里建議看官方的文檔比較靠譜. 中間件thunk是允許你向dispatch傳一個函數, 中間件LoggerMiddleware會記錄你的每一個action, 并且利用方法 console.group , 美觀的輸出.
好! Provider組件才是重點, Provider利用react的 context機制 , 將創建好的store暴露給app以及其所有后代. 這樣App的后代就能通過context訪問到store啦! 最后面那個if 就是webpack的webpack dev server插件的超炫功能---- 熱替換 .
因為store已經暴露到了整個App下, 所以所有組件可以調用store.dispatch來分發動作(數據流出組件), 也可以調用store.subscribe來訂閱(數據流入組件). 但是redux的設計者覺得這樣有點坑, 因為在react中, 有些組件沒有state, 只有props, 這樣的組件我們稱之為純組件. 于是乎, redux將組件分成了兩個部分, 容器組件和展示組件 .對于展示組件(純組件)并不需要狀態, 容器組件給出固定的props, 總會輸出一致的展示組件. 于是, state都放到了容器組件這里, 為了讓容器組件更好的獲取(輸出)store里的state. 就要使用connect. 上代碼!
Main.js
class Main extends Component {
createProfiles(profiles, repos) {
return profiles.map((profile, index) =>
<Profile key={index} {...profile} repo={repos[index]} />)
}
render() {
return (
<main>
{ this.createProfiles(this.props.profiles, this.props.repos) }
</main>
)
}
}
function mapStateToProps(state) {
return {
profiles: state.profiles,
repos: state.repos
}
}
export default connect(
mapStateToProps
)(Main)</code></pre>
最下面的connect是一個可以將Main組件包裝起來的函數, Main組件被包裝后會在外層新增一個新的組件包裹Main. mapStateToProps會在store的state發生變化的時候被調用, 其返回值會傳給Main組件作為props, 其實看名字就知道了.

0x05 數據的抓取.
這里用到了異步action, 直接上代碼!
actions.js
export const fetchProfiles = username => (dispatch, getState) => {
dispatch(requestProfile(username))
return fetch(https://api.github.com/users/${username}
)
.then((response) => {
// 檢查用戶是否存在
if (response.status !== 200) {
throw new Error('profiles fetch failed')
}
return fetch(`https://api.github.com/users/${username}/following`)
})
.then(response => response.json())
.then(following => {
return Promise.all(following.map(f => {
return fetch(`https://api.github.com/users/${f.login}`)
}))
})
.then(responses => Promise.all(responses.map(response => response.json())))
.then(followingUsers => {
const sortedUsers = followingUsers.sort((a, b) => b.followers - a.followers)
return sortedUsers.slice(0, 3)
})
.then((users) => {
dispatch(requestSuccess(users))
console.dir(users)
return Promise.all(users.map(user =>
fetch(`https://api.github.com/users/${user.login}/repos`)))
})
.then(responses => Promise.all(responses.map(res => res.json())))
.then((repos) => {
repos = repos.map(repo => repo.slice(0, 3))
dispatch(requestReposSuccess(repos))
})
.catch((err) => dispatch(requestFailed(err)))
}</code></pre>
這里才是最好玩(最坑)的地方, fetchProfiles函數是一個Action Creator,只要爬取數據, 這個函數就會被調用. 這里用到了各種then(旗幟鮮明的表示用好 Promise/A+ 規范真的是爽歪歪.)fetchProfiles會被傳入dispatch(用來分發之后的異步action)和getStore.倒數第n行的dispatch的調用就繼續發起一個異步action, 下一個action的代碼如下:
function requestReposSuccess(repos) {
repos = repos.map(repo => repo.sort((a, b) => b.stargazers_count - a.stargazers_count))
repos = repos.map(repo => repo.map(r => ({
star: r.stargazers_count,
name: r.name,
description: r.description.slice(0, 40) + ' ...'
})))
return {
type: REQUEST_REPOS_SUCCESS,
payload: repos
}
}</code></pre>
旗幟鮮明的表示es6的匿名函數的寫法真的是讓我像寫詩一樣寫代碼~
0x06. 最后
大概的就這么多, 不總結的話就不像是一篇文章了, 總結如下:
-
webpack確實是一個好東西, 要是能夠用上熱替換的話真的是太強大了, 怪不得能火成這鳥樣...其代碼分割也是很強大的
-
使用react中的context可以讓react自動將信息傳到子樹中的任何組件,但是組件必須設置contextTypes, 否則無法訪問對應的context.對于主題等這些全局信息應該使用context傳給子樹, 但是一些平常的state最好別傳, 原因 , 而且context的API不是穩定的, 今后可能會發生變化.
-
redux中store是唯一存儲狀態的容器(這也是和flux的不同之處), 還有容器組件和展示組件, redux通過provider注入store中的state到App中, 通過connect來構造容器組件, 方便組件更好的獲得(輸出)state, 同時限制展示組件只能從容器組件獲取state. 一個App中的容器組件可以有多個.
-
redux的中間件的用法及原理, 很強勢, 建議去看. 這是地址
-
thunk以及promise等中間件可以實現異步的action.
-
最后就是寫這個項目感覺很棒, es6+react+babel+webpack+redux 寫前端真的是酷比(苦逼)了!
參考文章:

來自:https://segmentfault.com/a/1190000007014604