Be Close To The Real World

Posted SharkChilli

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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\' })) ) }

 

以上是关于Be Close To The Real World的主要内容,如果未能解决你的问题,请参考以下文章

Welcome to the Real World

着色页(Color pictures to the real picture)

#My First Blog Diary : a jouney on cnblogs to be a real coder

The application was unable to start corretyy(0xc000007b) . Click OK to close the appliction

The plan have to be adjust ...

build a real-time analytics dashboard to visualize the number of orders getting shipped every minute