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

mugongfhnd 8年前
   <h3><strong>0x01. 这是一个什么玩意儿?</strong></h3>    <p>github上有太多太多的牛人, 这个东西可以帮助你通过给定的一个github的用户, 然后通过他关注的人, 找出他关注的人里的被关注数最高的几个。 然后不断的循环, 从而通过关系链找到github上最受欢迎的大神~ 这个东西还只是一个小东西, 如果你有兴趣, 可以fork这个小的不能再小的项目...</p>    <p>项目截图</p>    <p><img src="https://simg.open-open.com/show/9ac9bfc61f7eea848bc44cc5d71f8bd3.jpg"></p>    <h3><strong>0x02. 为什么要做这个东西?</strong></h3>    <p>一来是自己确实想做着玩一玩, 还有就是这个项目用到了react + redux. 想进一步的熟悉redux这个玩意儿。</p>    <h3><strong>0x03. 开工开工~ 搭建环境</strong></h3>    <p>用到的工具:webpack. 直接上配置</p>    <pre>  <code class="language-javascript">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>    <h3><strong>0x04. 使用 <a href="/misc/goto?guid=4959717446998433794" rel="nofollow,noindex">react-redux</a></strong></h3>    <p>当我刚开始使用react-redux的时候, 我的内心是绝望的. 什么connect, provider, 各种props映射, 各种dispatch映射.但是毕竟就是想拿这个东西顺便学习一下react-redux. 话不多说, 上代码!</p>    <p>entry.js</p>    <pre>  <code class="language-javascript">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>    <p>这里用到了 <a href="/misc/goto?guid=4959671500815860020" rel="nofollow,noindex">redux中间件</a> 的概念, 这里建议看官方的文档比较靠谱. 中间件thunk是允许你向dispatch传一个函数, 中间件LoggerMiddleware会记录你的每一个action, 并且利用方法 <a href="/misc/goto?guid=4959717447130031905" rel="nofollow,noindex">console.group</a> , 美观的输出.</p>    <p>好! Provider组件才是重点, Provider利用react的 <a href="/misc/goto?guid=4959714779314212813" rel="nofollow,noindex">context机制</a> , 将创建好的store暴露给app以及其所有后代. 这样App的后代就能通过context访问到store啦! 最后面那个if 就是webpack的webpack dev server插件的超炫功能---- <a href="/misc/goto?guid=4959717447231368755" rel="nofollow,noindex">热替换</a> .</p>    <p>因为store已经暴露到了整个App下, 所以所有组件可以调用store.dispatch来分发动作(数据流出组件), 也可以调用store.subscribe来订阅(数据流入组件). 但是redux的设计者觉得这样有点坑, 因为在react中, 有些组件没有state, 只有props, 这样的组件我们称之为纯组件. 于是乎, redux将组件分成了两个部分, <a href="/misc/goto?guid=4959717446998433794" rel="nofollow,noindex">容器组件和展示组件</a> .对于展示组件(纯组件)并不需要状态, 容器组件给出固定的props, 总会输出一致的展示组件. 于是, state都放到了容器组件这里, 为了让容器组件更好的获取(输出)store里的state. 就要使用connect. 上代码!</p>    <p>Main.js</p>    <pre>  <code class="language-javascript">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>    <p>最下面的connect是一个可以将Main组件包装起来的函数, Main组件被包装后会在外层新增一个新的组件包裹Main. mapStateToProps会在store的state发生变化的时候被调用, 其返回值会传给Main组件作为props, 其实看名字就知道了.</p>    <p><img src="https://simg.open-open.com/show/811cbcb2bca1635fa0ae2a2aa9f2e096.jpg"></p>    <h3><strong>0x05 数据的抓取.</strong></h3>    <p>这里用到了异步action, 直接上代码!</p>    <p>actions.js</p>    <pre>  <code class="language-javascript">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>    <p>这里才是最好玩(最坑)的地方, fetchProfiles函数是一个Action Creator,只要爬取数据, 这个函数就会被调用. 这里用到了各种then(旗帜鲜明的表示用好 <a href="/misc/goto?guid=4958866170093509700" rel="nofollow,noindex">Promise/A+</a> 规范真的是爽歪歪.)fetchProfiles会被传入dispatch(用来分发之后的异步action)和getStore.倒数第n行的dispatch的调用就继续发起一个异步action, 下一个action的代码如下:</p>    <pre>  <code class="language-javascript">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>    <p>旗帜鲜明的表示es6的匿名函数的写法真的是让我像写诗一样写代码~</p>    <h3><strong>0x06. 最后</strong></h3>    <p>大概的就这么多, 不总结的话就不像是一篇文章了, 总结如下:</p>    <ul>     <li> <p>webpack确实是一个好东西, 要是能够用上热替换的话真的是太强大了, 怪不得能火成这鸟样...其代码分割也是很强大的</p> </li>     <li> <p>使用react中的context可以让react自动将信息传到子树中的任何组件,但是组件必须设置contextTypes, 否则无法访问对应的context.对于主题等这些全局信息应该使用context传给子树, 但是一些平常的state最好别传, <a href="/misc/goto?guid=4959717447361316400" rel="nofollow,noindex">原因</a> , 而且context的API不是稳定的, 今后可能会发生变化.</p> </li>     <li> <p>redux中store是唯一存储状态的容器(这也是和flux的不同之处), 还有容器组件和展示组件, redux通过provider注入store中的state到App中, 通过connect来构造容器组件, 方便组件更好的获得(输出)state, 同时限制展示组件只能从容器组件获取state. 一个App中的容器组件可以有多个.</p> </li>     <li> <p>redux的中间件的用法及原理, 很强势, 建议去看. <a href="/misc/goto?guid=4959671500815860020" rel="nofollow,noindex">这是地址</a></p> </li>     <li> <p>thunk以及promise等中间件可以实现异步的action.</p> </li>     <li> <p>最后就是写这个项目感觉很棒, es6+react+babel+webpack+redux 写前端真的是酷比(苦逼)了!</p> </li>    </ul>    <p> </p>    <p><strong>参考文章:</strong></p>    <ul>     <li> <p><a href="/misc/goto?guid=4959717447452624719" rel="nofollow,noindex">http://jiavan.com/react-async...</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959717447536289172" rel="nofollow,noindex">http://www.ruanyifeng.com/blo...</a></p> </li>    </ul>    <p> </p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/023839232c83695b84d108d24fa5777a.jpg"></p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007014604</p>    <p> </p>