ES6, React, Redux, Webpack寫的一個爬 GitHub 的網頁

mugongfhnd 8年前發布 | 21K 次閱讀 Redux 網絡爬蟲 Github webpack

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

 

 本文由用戶 mugongfhnd 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!