redux实现real-world
已经写完好几天了,就是没有写博客,先大概写一下吧。what:它是一个通过输入github用户名,进行搜索此用户名下的操作。How:项目用create-react-app脚手架。添加一些redux的依赖。项目结构package.json文件(如果要用,可以直接粘贴,这经过测试的,不会出现版本冲突问题){"name": &am
已经写完好几天了,就是没有写博客,先大概写一下吧。
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即可。
2. TypeError: Super expression must either be null or a function, not undefined
注:说明你`extend`的那个函数没有导出相应的属性。 3. react-router出错:TypeError: (0 , _reactRouter2.default) is not a function
**解决:**将`import createMemoryHistory from "react-router"; ` 改为 `import {createMemoryHistory} from "react-router";`
4. Uncaught TypeError: (0, _reactRouterREdux.syncHistoryWithStore) is not a function
解决:npm install react-router-redux@4.0.8
5. 无状态组件问题:会在render处报错。 **解决**:无状态组件是一个函数,而不是一个类,所以不能直接render,我们只需要return就ok. `expott default(props)=>{return (
)}` 6. 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 of `Me`. **解决:**可能需要降低`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目录下面的。
![这里写图片描述](https://img-blog.csdn.net/20180520191038626?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5MDgzMDA0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 当然也很清楚,就是将我们的redux的五大部分代码分别放置各自的文件夹下面,`actions`,`compontents`(分为容器组件`containers`和展示组件`components`),`middleware`,`reducers`和`store`.
先回顾一下各个部分都是干嘛的:
(1)actions
actions中我们放置着可能会发起的一系列操作action.在此项目中,我们需要调用异步API(即发起请求的时刻,接受相应的时刻),这两个时刻都会更改应用的state,所以每个API请求至少有三种action。
- 通知reducer请求开始的action,对于这种action,raducer可能会切换一下state中的isFetching标记。以此来告诉UI来显示加载页面。
- 通知reducer请求成功的action。对于这种action,reducer可能会把接收到的新数据和并到state中,并且重置isFetching。UI则会隐藏加载界面,并显示接收到的数据。
- 通知reducer请求失败的action,对于这种action,reducer会重置isFetching.另外,有些reducer会保存这些失败的信息,并在ui中显示出来。
在此此项目中:我们主要有三个异步action,[UserLogin]请求,[Repo]请求,[Starred]请求。
//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;
注意:
- 我们用Object.assign()来高效的更新state.。处理数据的突变(指直接修改引用所指向的值,而引用本身保持不变),将那些无需修改的项原封不动的放入里面。
- 我们将太大的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(){...}`必须用一个`
(5)store
应用中所有的数据流都遵循相同的生命周期,可以让应用变得更加预测和理解.哇哇哇,我不想写了.先上代码,当然这也是在生产模式下的代码.
//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 …那就加完之后再写吧.
结束!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)