zoukankan      html  css  js  c++  java
  • Be Close To The Real World

    ps: 最近在学react和redux。用了几天时间看了下,个人觉得react本身比较易懂,但是涉及到的各种中间件和api让人脑阔疼(正好前段时间用vue+koa写了个简易的博客,对中间件还算理解)。在看到redux的时候,被这个real world的栗子难住了(个人水平太low)还2天过年,心思也不在这了。本来想放弃了,后来想想年后就辞职找工作了(心芳芳)新的风暴已经出现,怎么能够停滞不前。硬着头皮看下代码吧!

    栗子目录是酱紫的....,咱们剥洋葱(衣服)一样,慢慢深入。

    根目录下的index.js ( Root模板,store数据和逻辑 ) 

    import React from 'react' 
    import { render } from 'react-dom'  
    import { BrowserRouter as Router } from 'react-router-dom'
    import Root from './containers/Root'
    import configureStore from './store/configureStore'
    
    const store = configureStore(); 
    
    render(
      <Router>
        <Root store={store} />  // 由顶层传入store,谨记redux三大原则(单一store,store改变必须由dispatch来通知,reducer最好是pure函数
    ) </Router>, document.getElementById('root') );

    /containers/Root ( 就2行代码,根据运行环境切换输出 ) 

    if (process.env.NODE_ENV === 'production') {
      module.exports = require('./Root.prod')
    } else {
      module.exports = require('./Root.dev')
    }

    /containers/Root.dev.js 

    import React from 'react'
    import PropTypes from 'prop-types'  // props类型检查
    import { Provider } from 'react-redux' // 包装函数,用于将redux和react链接起来
    import DevTools from './DevTools'  // 可视化调试工具,用法简单
    import { Route } from 'react-router-dom'  // 路由
    import App from './App'
    import UserPage from './UserPage'
    import RepoPage from './RepoPage'
    
    const Root = ({ store }) => (
      <Provider store={store}>
        <div>
          <Route path="/" component={App} />
          <Route path="/:login/:name" component={RepoPage} />
          <Route path="/:login" component={UserPage} />
          <DevTools />
        </div>
      </Provider>
    )
    
    Root.propTypes = {
      store: PropTypes.object.isRequired,
    }
    
    export default Root

    /containers/APP.js ( 顶层逻辑,react是单向数据流,由高流向低( 父->子->孙 ) )

    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    import { connect } from 'react-redux'
    import { withRouter } from 'react-router-dom'
    import Explore from '../components/Explore'
    import { resetErrorMessage } from '../actions'

    class App extends Component {
    static propTypes = {
    // Injected by React Redux
    errorMessage: PropTypes.string,
    resetErrorMessage: PropTypes.func.isRequired,
    inputValue: PropTypes.string.isRequired,
    // Injected by React Router
    children: PropTypes.node  // 由路由注入?怎么个注入法? 貌似是<tmp><h1>hello wrold</h1></tmp>
    };

    handleDismissClick = e => {
    this.props.resetErrorMessage();
    e.preventDefault()
    };

    handleChange = nextValue => {
    this.props.history.push(`/${nextValue}`)
    };

    renderErrorMessage() {
    const { errorMessage } = this.props;
    if (!errorMessage) {
    return null
    }

    return (
    <p style={{ backgroundColor: '#e99', padding: 10 }}>
    <b>{errorMessage}</b>
    {' '}
    <button onClick={this.handleDismissClick}>
    Dismiss
    </button>
    </p>
    )
    }

    render() {
    const { children, inputValue } = this.props;
    return (
    <div>
    <Explore value={inputValue}
    onChange={this.handleChange} />
    <hr />
    {this.renderErrorMessage()}
    {children}
    </div>
    )
    }
    }

    const mapStateToProps = (state, ownProps) => ({  // 订阅Store,每当state变化时就会重新渲染。第一个参数是Store,第二个参数是当前组件的props对象,props改变也会重新渲染
    errorMessage: state.errorMessage,
    inputValue: ownProps.location.pathname.substring(1)  // 必须要ownProps.location.pathname才能拿到path吗,反正path变化会触发渲染,类似Vue的watch router
    });

    export default withRouter(connect(mapStateToProps, {  // withRouter可以包装任何自定义组件,无需一级级传递react-router属性,就可以拿到需要的路由信息
    resetErrorMessage
    })(App)

    /components/Explore.js ( 上面的App.js引入了2个模块,actions和Explore,我们先看下Explore  )

    /* eslint-disable no-undef */
    
    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    
    const GITHUB_REPO = 'https://github.com/reactjs/redux';
    
    export default class Explore extends Component {
      static propTypes = {
        value: PropTypes.string.isRequired,    // 这里有2个props,都是App传过来的
        onChange: PropTypes.func.isRequired
      };
    
      componentWillReceiveProps(nextProps) {            // 生命周期钩子事件,当组件接收props时触发
        if (nextProps.value !== this.props.value) {
          this.setInputValue(nextProps.value)
        }
      }
    
      getInputValue = () => {
        return this.input.value
      };
    
      setInputValue = (val) => {
        // Generally mutating DOM is a bad idea in React components,
        // but doing this for a single uncontrolled field is less fuss
        // than making it controlled and maintaining a state for it.
        this.input.value = val
      };
    
      handleKeyUp = (e) => {
        if (e.keyCode === 13) {
          this.handleGoClick()
        }
      };
    
      handleGoClick = () => {
        this.props.onChange(this.getInputValue())
      };
    
      render() {
        return (
          <div>
            <p>Type a username or repo full name and hit 'Go':</p>  // 可以看到这个组件就干了1件事,调用父组件的onChange事件,而这个onChange事件是用来改变路由的
            <input size="45"
                   ref={(input) => this.input = input}
                   defaultValue={this.props.value}
                   onKeyUp={this.handleKeyUp} />
            <button onClick={this.handleGoClick}>
              Go!
            </button>
            <p>
              Code on <a href={GITHUB_REPO} target="_blank" rel="noopener noreferrer">Github</a>.
            </p>
            <p>
              Move the DevTools with Ctrl+W or hide them with Ctrl+H.
            </p>
          </div>
        )
      }
    }

    App.js是按需引入的actions的resetErrorMessage函数


    export const resetErrorMessage = () => ({
    type: RESET_ERROR_MESSAGE
    });
    // 就是dispatch一个RESET_ERROR_MESSAGE,我们来看看这个action的对应的reducer是怎么样的
    const errorMessage = (state = null, action) => {
    const { type, error } = action;

    if (type === ActionTypes.RESET_ERROR_MESSAGE) {
    return null
    } else if (error) {
    return error
    }

    return state
    };
    // 这个简单,就是返回对应的错误信息或者null

    App.js到此结束,下面看另一个大组件RepoPage.js

    /* eslint-disable no-undef */
    
    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    import { connect } from 'react-redux'
    import { withRouter } from 'react-router-dom'
    import { loadRepo, loadStargazers } from '../actions'    // 这里可以看到引入了5个模块,actions2个,子组件3个。
    import Repo from '../components/Repo'
    import User from '../components/User'
    import List from '../components/List'
    
    const loadData = props => {
      const { fullName } = props;
      props.loadRepo(fullName, [ 'description' ]);
      props.loadStargazers(fullName)
    };
    
    class RepoPage extends Component {
      static propTypes = {
        repo: PropTypes.object,
        fullName: PropTypes.string.isRequired,
        name: PropTypes.string.isRequired,
        owner: PropTypes.object,
        stargazers: PropTypes.array.isRequired,
        stargazersPagination: PropTypes.object,
        loadRepo: PropTypes.func.isRequired,
        loadStargazers: PropTypes.func.isRequired
      };
    
      componentWillMount() {
        loadData(this.props)
      }
    
      componentWillReceiveProps(nextProps) {
        if (nextProps.fullName !== this.props.fullName) {
          loadData(nextProps)
        }
      }
    
      handleLoadMoreClick = () => {
        this.props.loadStargazers(this.props.fullName, true)
      };
    
      renderUser(user) {
        return <User user={user} key={user.login} />
      }
    
      render() {
        const { repo, owner, name } = this.props;
        if (!repo || !owner) {
          return <h1><i>Loading {name} details...</i></h1>
        }
    
        const { stargazers, stargazersPagination } = this.props;
        return (
          <div>
            <Repo repo={repo}
                  owner={owner} />
            <hr />
            <List renderItem={this.renderUser}
                  items={stargazers}
                  onLoadMoreClick={this.handleLoadMoreClick}
                  loadingLabel={`Loading stargazers of ${name}...`}
                  {...stargazersPagination} />
          </div>
        )
      }
    }
    
    const mapStateToProps = (state, ownProps) => {
      const login = ownProps.match.params.login.toLowerCase();
      const name = ownProps.match.params.name.toLowerCase();
    
      const {
        pagination: { stargazersByRepo },
        entities: { users, repos }
      } = state;
    
      const fullName = `${login}/${name}`;
      const stargazersPagination = stargazersByRepo[fullName] || { ids: [] };
      const stargazers = stargazersPagination.ids.map(id => users[id]);
    
      return {
        fullName,
        name,
        stargazers,
        stargazersPagination,
        repo: repos[fullName],
        owner: users[login]
      }
    };
    
    export default withRouter(connect(mapStateToProps, {
      loadRepo,
      loadStargazers
    })(RepoPage))

    /actions/index.js(所有dispatch的type)

    import { CALL_API, Schemas } from '../middleware/api'  // 这里引入了另一个模块
    
    export const USER_REQUEST = 'USER_REQUEST';
    export const USER_SUCCESS = 'USER_SUCCESS';
    export const USER_FAILURE = 'USER_FAILURE';
    
    // Fetches a single user from Github API.
    // Relies on the custom API middleware defined in ../middleware/api.js.
    const fetchUser = login => ({
      [CALL_API]: {
        types: [ USER_REQUEST, USER_SUCCESS, USER_FAILURE ],
        endpoint: `users/${login}`,
        schema: Schemas.USER
      }
    });
    
    // Fetches a single user from Github API unless it is cached.
    // Relies on Redux Thunk middleware.
    export const loadUser = (login, requiredFields = []) => (dispatch, getState) => {
      const user = getState().entities.users[login];
      if (user && requiredFields.every(key => user.hasOwnProperty(key))) {
        return null
      }
    
      return dispatch(fetchUser(login))
    };
    
    export const REPO_REQUEST = 'REPO_REQUEST';
    export const REPO_SUCCESS = 'REPO_SUCCESS';
    export const REPO_FAILURE = 'REPO_FAILURE';
    
    // Fetches a single repository from Github API.
    // Relies on the custom API middleware defined in ../middleware/api.js.
    const fetchRepo = fullName => ({
      [CALL_API]: {
        types: [ REPO_REQUEST, REPO_SUCCESS, REPO_FAILURE ],
        endpoint: `repos/${fullName}`,
        schema: Schemas.REPO
      }
    });
    
    // Fetches a single repository from Github API unless it is cached.
    // Relies on Redux Thunk middleware.
    export const loadRepo = (fullName, requiredFields = []) => (dispatch, getState) => {
      const repo = getState().entities.repos[fullName];
      if (repo && requiredFields.every(key => repo.hasOwnProperty(key))) {
        return null
      }
    
      return dispatch(fetchRepo(fullName))
    };
    
    export const STARRED_REQUEST = 'STARRED_REQUEST';
    export const STARRED_SUCCESS = 'STARRED_SUCCESS';
    export const STARRED_FAILURE = 'STARRED_FAILURE';
    
    // Fetches a page of starred repos by a particular user.
    // Relies on the custom API middleware defined in ../middleware/api.js.
    const fetchStarred = (login, nextPageUrl) => ({
      login,
      [CALL_API]: {
        types: [ STARRED_REQUEST, STARRED_SUCCESS, STARRED_FAILURE ],
        endpoint: nextPageUrl,
        schema: Schemas.REPO_ARRAY
      }
    });
    
    // Fetches a page of starred repos by a particular user.
    // Bails out if page is cached and user didn't specifically request next page.
    // Relies on Redux Thunk middleware.
    export const loadStarred = (login, nextPage) => (dispatch, getState) => {
      const {
        nextPageUrl = `users/${login}/starred`,
        pageCount = 0
      } = getState().pagination.starredByUser[login] || {};
    
      if (pageCount > 0 && !nextPage) {
        return null
      }
    
      return dispatch(fetchStarred(login, nextPageUrl))
    };
    
    export const STARGAZERS_REQUEST = 'STARGAZERS_REQUEST';
    export const STARGAZERS_SUCCESS = 'STARGAZERS_SUCCESS';
    export const STARGAZERS_FAILURE = 'STARGAZERS_FAILURE';
    
    // Fetches a page of stargazers for a particular repo.
    // Relies on the custom API middleware defined in ../middleware/api.js.
    const fetchStargazers = (fullName, nextPageUrl) => ({
      fullName,
      [CALL_API]: {
        types: [ STARGAZERS_REQUEST, STARGAZERS_SUCCESS, STARGAZERS_FAILURE ],
        endpoint: nextPageUrl,
        schema: Schemas.USER_ARRAY
      }
    });
    
    // Fetches a page of stargazers for a particular repo.
    // Bails out if page is cached and user didn't specifically request next page.
    // Relies on Redux Thunk middleware.
    export const loadStargazers = (fullName, nextPage) => (dispatch, getState) => {
      const {
        nextPageUrl = `repos/${fullName}/stargazers`,
        pageCount = 0
      } = getState().pagination.stargazersByRepo[fullName] || {};
    
      if (pageCount > 0 && !nextPage) {
        return null
      }
    
      return dispatch(fetchStargazers(fullName, nextPageUrl))
    };
    
    export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE';
    
    // Resets the currently visible error message.
    export const resetErrorMessage = () => ({
        type: RESET_ERROR_MESSAGE
    });

    /middleware/api.js(异步请求数据)

    import { normalize, schema } from 'normalizr'
    import { camelizeKeys } from 'humps'
    
    const getNextPageUrl = response => {
    
      const link = response.headers.get('link');
      if (!link) {
        return null
      }
    
      const nextLink = link.split(',').find(s => s.indexOf('rel="next"') > -1);
      if (!nextLink) {
        return null
      }
    
      return nextLink.trim().split(';')[0].slice(1, -1)
    };
    
    const API_ROOT = 'https://api.github.com/';
    
    const callApi = (endpoint, schema) => {
      const fullUrl = (endpoint.indexOf(API_ROOT) === -1) ? API_ROOT + endpoint : endpoint;
    
      return fetch(fullUrl)
        .then(response =>
          response.json().then(json => {
            if (!response.ok) {
              return Promise.reject(json)
            }
            console.error(response.headers);
            const camelizedJson = camelizeKeys(json);
            const nextPageUrl = getNextPageUrl(response);
    
            return Object.assign({},
              normalize(camelizedJson, schema),
              { nextPageUrl }
            )
          })
        )
    };
    
    
    const userSchema = new schema.Entity('users', {}, {
      idAttribute: user => user.login.toLowerCase()
    });
    
    const repoSchema = new schema.Entity('repos', {
      owner: userSchema
    }, {
      idAttribute: repo => repo.fullName.toLowerCase()
    });
    
    // Schemas for Github API responses.
    export const Schemas = {
      USER: userSchema,
      USER_ARRAY: [userSchema],
      REPO: repoSchema,
      REPO_ARRAY: [repoSchema]
    };

    export const CALL_API = 'Call API'; export default store => next => action => { const callAPI = action[CALL_API]; if (typeof callAPI === 'undefined') { return next(action) } let { endpoint } = callAPI; const { schema, types } = callAPI; if (typeof endpoint === 'function') { endpoint = endpoint(store.getState()) } if (typeof endpoint !== 'string') { throw new Error('Specify a string endpoint URL.') } if (!schema) { throw new Error('Specify one of the exported Schemas.') } if (!Array.isArray(types) || types.length !== 3) { throw new Error('Expected an array of three action types.') } if (!types.every(type => typeof type === 'string')) { throw new Error('Expected action types to be strings.') } const actionWith = data => { const finalAction = Object.assign({}, action, data); delete finalAction[CALL_API]; return finalAction }; const [ requestType, successType, failureType ] = types; next(actionWith({ type: requestType })); return callApi(endpoint, schema).then( response => next(actionWith({ response, type: successType })), error => next(actionWith({ type: failureType, error: error.message || 'Something bad happened' })) ) }
  • 相关阅读:
    Semaphore类
    我的java学习之路五:java的循环和条件语句
    我的java学习之路四:java的基础类型和变量
    第一节 线性表
    我的java学习之路三:java的类与对象
    我的Java学习之路二:Java基础语法
    算法分析一:基本定义
    我的java学习之路一:java的安装以及环境配置
    【2019 CCPC 江西省赛】Cotree 树重心
    【2018 icpc 南京站】G
  • 原文地址:https://www.cnblogs.com/SharkChilli/p/8445228.html
Copyright © 2011-2022 走看看