/ react

ES6, React, Redux, Webpack写的一个爬 GitHub 的网页

find-github-star 开发历程:

项目地址 find-github-star

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' }),
  ]
}

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()
}

这里用到了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)

最下面的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)))
}

这里才是最好玩(最坑)的地方, 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
  }
}

旗帜鲜明的表示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 写前端真的是酷比(苦逼)了!

写这篇文章真的很不容易哈, 如果你觉得我写的对你有那么一点点的帮助, 可以选择关注我的新浪微博, Twitter, Github, Facebook, 个人主页, 个人主页还在备案, 马上开通~

参考文章:

完!~ 荆轲刺秦王~ ~~~~~~其实我不是王尼玛...
苦逼