redux实现real-world

已经写完好几天了,就是没有写博客,先大概写一下吧。


what:

它是一个通过输入github用户名,进行搜索此用户名下的操作。

How:

  1. 项目用create-react-app脚手架。添加一些redux的依赖。
  2. 项目结构
    这里写图片描述
    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-domreact的版本。但是在降低这些版本的时候,redux-devtools等一系列依赖包的版本又会出现问题。

启动项目的时候报错问题:

  1. 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 of Header.


    注:别人出现的都是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 [email protected]

  5. 无状态组件问题:会在render处报错。
    解决:无状态组件是一个函数,而不是一个类,所以不能直接render,我们只需要return就ok. expott default(props)=>{return (<div><div>)}
  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开发工具。

  1. redux-devtools:redux的开发工具包,而且DevTools支持自定义的monitor组件,所以我们完全可以自定义一个我们想要的monitor组件和UI展示风格。
  2. redux_devtools-log-monitor:这是reaux DevTools默认的monitor,它可以展示state和action的一系列信息,而且我们可以在monitor改变它的值。
  3. 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,reducersstore.

先回顾一下各个部分都是干嘛的:

(1)actions — actions中我们放置着可能会发起的一系列操作action.在此项目中,我们需要调用异步API(即发起请求的时刻,接受相应的时刻),这两个时刻都会更改应用的state,所以每个API请求至少有三种action。 > 1. **通知**reducer**请求开始**的action,对于这种action,raducer可能会切换一下state中的isFetching标记。以此来告诉UI来显示加载页面。 > 2. **通知**reducer**请求成功**的action。对于这种action,reducer可能会把接收到的新数据和并到state中,并且重置isFetching。UI则会隐藏加载界面,并显示接收到的数据。 > 3. **通知**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;

注意:
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(){…}`必须用一个`

`包裹.,注意数据的传递,用`{ }`括起来.

## 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;
  1. 用createStore创建store,其中用applyMiddleware来创建异步数据树.用compose将异步数据树和同步数据树结合,最后生成一颗单一的store树.
  2. 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 …那就加完之后再写吧.

结束!

猜你喜欢

转载自blog.csdn.net/qq_39083004/article/details/80370160