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'}))) }
?
轉載于:https://www.cnblogs.com/SharkChilli/p/8445228.html
總結
以上是生活随笔為你收集整理的Be Close To The Real World的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ionic实现下载文件并打开功能(fil
- 下一篇: 深入浅出面向对象和原型【番外篇——重新认