已经写完好几天了,就是没有写博客,先大概写一下吧。
what:
它是一个通过输入github用户名,进行搜索此用户名下的操作。
How:
- 项目用create-react-app脚手架。添加一些redux的依赖。
- 项目结构
package.json文件:
(如果要用,可以直接粘贴,然后npm install
即可,这经过测试的,不会出现版本冲突问题)
{
"name": "real-world",
"version": "0.0.1",
"private": true,
"devDependencies": {
"react-scripts": "^1.1.4",
"redux-devtools": "^3.4.1",
"redux-devtools-dock-monitor": "^1.1.3",
"redux-devtools-log-monitor": "^1.4.0",
"redux-logger": "^3.0.6"
},
"dependencies": {
"humps": "^2.0.0",
"lodash": "^4.17.5",
"normalizr": "^3.2.4",
"prop-types": "^15.6.1",
"react": "^16.3.1",
"react-dom": "^16.3.1",
"react-redux": "^5.0.7",
"react-router-dom": "^4.1.2",
"redux": "^3.5.2",
"redux-thunk": "^2.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject",
"test": "react-scripts test"
}
}
4.代码的实现,已上传至Github。
5.遇到的坑。
更多的是 node版本的问题,ubuntu下node的版本是极低的,所以在没有升级之前。我们可能要降低
react-dom
和react
的版本。但是在降低这些版本的时候,redux-devtools
等一系列依赖包的版本又会出现问题。
启动项目的时候报错问题:
- warning: Failed Context Types: Calling PropTypes validators directly is not supported by the
prop-types
package. Use
PropTypes.checkPropTypes()
to call them. Read more at
http://fb.me/use-check-prop-types Check the render method ofHeader
.
注:别人出现的都是warning,而我的直接是error,但经最后检测,这个和页面的显示并没有联系。出现这个warning,升级react和react-dom即可。
- TypeError: Super expression must either be null or a function, not undefined
注:说明你
extend
的那个函数没有导出相应的属性。- react-router出错:TypeError: (0 , _reactRouter2.default) is not a function
解决:将
import createMemoryHistory from
改为
"react-router";import {createMemoryHistory} from
"react-router";- Uncaught TypeError: (0, _reactRouterREdux.syncHistoryWithStore) is not a function
解决:npm install [email protected]
- 无状态组件问题:会在render处报错。
解决:无状态组件是一个函数,而不是一个类,所以不能直接render,我们只需要return就ok.expott default(props)=>{return (<div><div>)}
- reactJS报错: Element type is invalid: expected a string (from built-in components) or a class/function (for composite components)
but got: undefined. Check the render method ofMe
.
解决:可能需要降低react-router
版本。
6. 项目启动
npm start
;项目打包
npm built
;
7. Redux-Devtools超酷的redux开发工具。
- redux-devtools:redux的开发工具包,而且DevTools支持自定义的monitor组件,所以我们完全可以自定义一个我们想要的monitor组件和UI展示风格。
- redux_devtools-log-monitor:这是reaux DevTools默认的monitor,它可以展示state和action的一系列信息,而且我们可以在monitor改变它的值。
- redux-devtools-dock-monitor:这monitor支持键盘的快捷键改变tree view在浏览器中的展示位置,
ctrl+h
可以隐藏tree view在浏览器中的显示。
注:我们在我们的containers文件夹下建立DevTools.js
文件,然后将其引入index.js文件即可。
// DevTools.js文件,我设定了用ctrl+w来改变tree view的位置,用ctrl+h来隐藏它。
import React from 'react'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
export default createDevTools(
<DockMonitor toggleVisibilityKey="ctrl-h"
changePositionKey="ctrl-w">
<LogMonitor />
</DockMonitor>
)
图片展示:
我们可以清楚的看到action和state的变化状态。
8. 接下来就是我们代码的实现和整合。我们将所有的源码都放在src目录下面的。
当然也很清楚,就是将我们的redux的五大部分代码分别放置各自的文件夹下面,actions
,compontents
(分为容器组件containers
和展示组件components
),middleware
,reducers
和store
.
先回顾一下各个部分都是干嘛的:
//UserLogin
const fetchUser = login => ({
[CALL_API]: {
types: [USER_REQUEST, USER_SUCCESS, USER_FAILURE],
endpoint: `users/${login}`,
schema: Schemas.USER
}
});
//Repo
const fetchRepo = fullName => ({
[CALL_API]: {
types: [REPO_REQUEST, REPO_SUCCESS, REPO_FAILURE],
endpoint: `repos/${fullName}`,
schema: Schemas.REPO
}
});
//starred
const fetchStarred = (login, nextPageUrl) => ({
login,
[CALL_API]: {
types: [STARRED_REQUEST, STARRED_SUCCESS, STARRED_FAILURE],
endpoint: nextPageUrl,
schema: Schemas.REPO_ARRAY
}
});
// 重置fetch错误信息的action
export const resetErrorMessage = () => ({
type: RESET_ERROR_MESSAGE
});
(2) reducers
接下来就是action的处理Reducers部分。此块主要包含两个部分,一个是对action的处理,一个是对分页的处理。我们都知道,reducers指定应用状态的变化如何相应actions并发送到store的。它接受(prestate,action)=>newstate;
注意:
1. 我们用Object.assign()来高效的更新state.。处理数据的突变(指直接修改引用所指向的值,而引用本身保持不变),将那些无需修改的项原封不动的放入里面。
2. 我们将太大的reducers拆分,最后用其combineReducers()
合成,生成一个函数,这个函数用来调用一系列reducer,每个reducer根据它们的key来筛选state中的一部分数据并进行处理,最后生成的这个函数将所有的reducer结果合成一个更大的对象。
eg:
import * as ActionTypes from '../actions';//得到一个以他们的名字作为key的Obj.
import merge from 'lodash/merge';
import paginate from './paginate';
import {combineReducers} from 'redux';
...
...
const rootReducer = combineReducers({
entities,
pagination,
errorMessage,
});
(3) middleware
在着,就是数据流的处理,我们一般用createStore
处理同步数据流,处理异步数据流只能用middleware
中间件,来增强dispatch
。
1.最简单的就是
applyMiddleware()
来处理。
2.当然还有好几种做法。看之前的middleware博客把。
3. 在此项目中,middleware文件中主要存放异步请求所需的api,和对Api的处理逻辑。
eg:(仅仅是其中的一部分代码)
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)
}
const camelizedJson = camelizeKeys(json)
const nextPageUrl = getNextPageUrl(response)
return Object.assign({},
normalize(camelizedJson, schema),
{nextPageUrl}
)
})
)
};
//最后的返回函数中
return callApi(endpoint, schema).then(
response => next(actionWith({
response,
type: successType
})),
error => next(actionWith({
type: failureType,
error: error.message || "Something bad happened"
}))
)
4.components
组件有容器组件和显示组件之分。
容器组件:作用就是描述如何运行(即数据如何获取,状态如何更新)它的数据来源主要是对
Redux state
的监听得到。调用方式主要是由React Redux生成,所以修改向Redux派发actions。
展示组件:作用就是如何展示页面骨架和样式。数据来源就是父子组件数据的传递props,一般就手动调用,数据的修改,即从props调用回调函数。
注:需要注意的是,
**在容器组件中**,我们进行router的分配。当然分的比较清除的还会由开发模式下和生成模式下。
//Root.dev.js
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;
当然这块容器组件中也包含了DevTools
组件,上面有提到到过。
最后用一个`App.js文件`将所有组件整合,包括展示组件,将其导出.
展示组件中:`Explore`组件,`Repo`组件,`User`组件,`List`组件.当然这块需要注意的就是各个组件中 的`render(){…}`必须用一个`
//configureStore.dev.js
import ...
const configureStore = preloadedState => {
const store = createStore(
rootReducer, preloadedState,
compose(
applyMiddleware(thunk, api, createLogger()),
DevTools.instrument()
)
);
if (module.hot) {
module.hot.accept('../reducers', () => {
store.replaceReducer(rootReducer);
});
}
return store;
};
export default configureStore;
- 用createStore创建store,其中用applyMiddleware来创建异步数据树.用compose将异步数据树和同步数据树结合,最后生成一颗单一的store树.
- Redux store保存根reducer返回完整的state数,用新的state来更新UI.
6.index.js
最后就是index.js文件啊,引入store,对整个页面进行渲染就ok.
import ...
const store = configureStore();
render(
<Router>
<Root store={store} />
</Router>,
document.getElementById('root')
)
基本完了,最后想着要不要加点样式,em …那就加完之后再写吧.