博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
我的第一个 react redux demo
阅读量:7180 次
发布时间:2019-06-29

本文共 11310 字,大约阅读时间需要 37 分钟。

最近学习react redux,先前看过了几本书和一些博客之类的,感觉还不错,比如《深入浅出react和redux》,《React全栈++Redux+Flux+webpack+Babel整合开发》,《React与Redux开发实例精解》, 个人觉得《深入浅出react和redux》这本说讲的面比较全, 但是 很多都是蜻蜓点水, 不怎么深入。这里简单记录一个redux 的demo, 主要方便以后自己查看,首先运行界面如下:

 

项目结构如下:

 

这里我们一共有2个组件,都在components文件夹下面,Picker.js实现如下:

import React from 'react';import PropTypes from 'prop-types';function Picker({value, onChange, options}) {    return(                    

{value}

);}Picker.propTypes = { options:PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, value:PropTypes.string.isRequired, onChange:PropTypes.func.isRequired,};export default Picker;这里的onChange事件是传递进来的,最终就是要触发一个action,Posts.js的实现如下:
import React from 'react';import PropTypes from 'prop-types';function Posts ({posts}){    return(        
    { posts.map((p,i)=>
  • {p.title}
  • ) }
);}Posts.propTypes = { posts:PropTypes.array.isRequired,};export default Posts;
  1. 现在来看看actions/index.js的实现:
import fetch from 'isomorphic-fetch';export const REQUEST_POSTS = 'REQUEST_POSTS';export const RECEIVE_POSTS = 'RECEIVE_POSTS';export const SELECT_REDDIT = 'SELECT_REDDIT';export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT';export function selectReddit(reddit){    return{        type:SELECT_REDDIT,        reddit,    };}export function invalidateReddit(reddit){    return {        type:INVALIDATE_REDDIT,        reddit,    };}export function requestPosts(reddit){    return {        type:REQUEST_POSTS,        reddit,    };}export function receivePosts(reddit,json){    return{        type:RECEIVE_POSTS,        reddit,        posts:json.data.children.map(x=>x.data),        receivedAt:Date.now(),    };}function fetchPosts(reddit){    return dispatch=>{        dispatch(requestPosts);        return fetch(`https://www.reddit.com/r/${reddit}.json`)        .then(r=>r.json())        .then(j=>dispatch(receivePosts(reddit,j)));    }}function shouldFetchPosts(state,reddit){    const posts = state.postsByReddit[reddit];    if(!posts){        return true;    }    if(posts.isFetching){        return false;    }    return posts.didInvalidate;}export function fetchPostsIfNeeded(reddit){    return (dispatch,getState)=>{        if(shouldFetchPosts(getState(),reddit)){            return dispatch(fetchPosts(reddit));        }        return null;    };}

主要是暴露出selectReddit,invalidateReddit,requestPosts,receivePosts和fetchPostsIfNeeded几个action,而fetchPostsIfNeeded才是主要的,首先调用shouldFetchPosts方法来检查是否需要获取数据, 如果是的话就调用fetchPosts方法,而fetchPosts方法返回的是一个function,这里我的项目使用了redux-thunk, 看看redux-thunk的实现如下:

function createThunkMiddleware(extraArgument) {  return ({ dispatch, getState }) => next => action => {    if (typeof action === 'function') {      return action(dispatch, getState, extraArgument);    }    return next(action);  };}const thunk = createThunkMiddleware();thunk.withExtraArgument = createThunkMiddleware;export default thunk;

所以在fetchPostsIfNeeded中的dispatch(fetchPosts(reddit)) 最终会进入到redux-thunk里面,fetchPosts(reddit)返回的是一个function如下,所以这里会进入这个action里面,也就是 return action(dispatch, getState, extraArgument);

function fetchPosts(reddit){    return dispatch=>{        dispatch(requestPosts);        return fetch(`https://www.reddit.com/r/${reddit}.json`)        .then(r=>r.json())        .then(j=>dispatch(receivePosts(reddit,j)));    }}

所以在fetchPosts里面的dispatch参数就是redux-thunk里面return action(dispatch, getState, extraArgument) 的dispatch

在这里的function里面, 一般发起http请求前有一个 状态改变(dispatch(requestPosts);), http请求成功后有一个 状态改变(dispatch(receivePosts(reddit,j))),失败也会有状态改变(这里忽律失败的case)

接下来看看containers\App.js

import React, { Component } from 'react';import PropTypes from 'prop-types';import { connect } from 'react-redux';import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions';import Picker from '../components/Picker';import Posts from '../components/Posts';class App extends Component{    constructor(props){        super(props);        this.handleChange=this.handleChange.bind(this);        this.handleRefreshClick=this.handleRefreshClick.bind(this);    }    componentDidMount(){        console.log('执行componentDidMount');        const { dispatch, selectedReddit } = this.props;        dispatch(fetchPostsIfNeeded(selectedReddit));    }    componentWillReceiveProps(nextProps){        console.log('执行componentWillReceiveProps', nextProps);        if(nextProps.selectedReddit !==this.props.selectedReddit)        {            const { dispatch, selectedReddit } = this.props;            dispatch(fetchPostsIfNeeded(selectedReddit));        }    }    handleChange(nextReddit){        this.props.dispatch(selectReddit(nextReddit));    }    handleRefreshClick(e){        e.preventDefault();        const {dispatch, selectedReddit } = this.props;        dispatch(invalidateReddit(selectedReddit));        dispatch(fetchPostsIfNeeded(selectedReddit));    }    render(){        const { selectedReddit, posts, isFetching, lastUpdated } = this.props;        const isEmpty = posts.length === 0;        const message = isFetching ? 

Loading...

:

Empty.

; return(

{ lastUpdated && Last updated at {

new Date(lastUpdated).toLocaleDateString()}.} {
!isFetching && Refresh}

{isEmpty?message:
}
); }}App.propTypes = { selectedReddit:PropTypes.string.isRequired, posts:PropTypes.array.isRequired, isFetching:PropTypes.bool.isRequired, lastUpdated:PropTypes.number, dispatch:PropTypes.func.isRequired};function mapStateToProps(state){ const { selectedReddit, postsByReddit } = state; const { isFetching, lastUpdated, items: posts,} = postsByReddit[selectedReddit] || { isFetching: true, items: [], }; return { selectedReddit, posts, isFetching, lastUpdated, };}export default connect(mapStateToProps)(App);

只是要注意一下componentDidMount方法里面是调用dispatch(fetchPostsIfNeeded(selectedReddit));的,页面加载后就发送默认的http请求。

在来看看reducers\index.js:

import {combineReducers} from 'redux';import {SELECT_REDDIT,INVALIDATE_REDDIT,REQUEST_POSTS,RECEIVE_POSTS} from '../actions';function selectedReddit (state='reactjs',action) {    switch(action.type){        case SELECT_REDDIT:            return action.reddit;        default:        return state === undefined ? "" : state;    }}function posts(state= { isFetching: false, didInvalidate: false,items: [],}, action){    switch(action.type){        case INVALIDATE_REDDIT:            return Object.assign({},state,{didInvalidate:true});        case REQUEST_POSTS:            return Object.assign({},state,{isFetching:true,didInvalidate:false});        case RECEIVE_POSTS:            return Object.assign({},state,{isFetching:false,didInvalidate:false,items:action.posts,lastUpdated:action.receivedAt});        default:            return state;    }}function postsByReddit(state={},action){    switch(action.type){        case INVALIDATE_REDDIT:        case RECEIVE_POSTS:        case REQUEST_POSTS:            return Object.assign({},state,{[action.reddit]:posts(state[action.reddit],action)});        default:            return state === undefined ? {} : state;    }}const rootReducer =combineReducers({postsByReddit,selectedReddit});export default rootReducer;

这里用combineReducers来合并了postsByReddit和selectedReddit两个Reducers,所以每个action都会进入这2个Reducers(也不知道我的理解是否正确),比如action type 是INVALIDATE_REDDIT,selectedReddit 什么都不处理,直接返回state,然而postsByReddit会返回我们需要的state。 还有就是经过combineReducers合并后的数据,原先postsByReddit需要的state现在就只能通过state.postsByReddit来获取了。

还有大家主要到了没有, 这里有return state === undefined ? "" : state; 这样的写法, 那是combineReducers在初始化的时候会传递undefined ,combineReducers->assertReducerShape的实现如下:

所以默认的state传递的是undefined,而我们的reducer也是没有处理ActionTypes.INIT的

现在来看看store/configureStore.js

import {createStore,applyMiddleware,compose} from 'redux';import thunkMiddleware from 'redux-thunk';import  logger from 'redux-logger';import rootReducer from '../reducers';const store=createStore(rootReducer,initialState,compose(        applyMiddleware(thunkMiddleware,logger),        window.devToolsExtension? window.devToolsExtension():f=>f    ));    if(module.hot){        module.hot.accept('../reducers',()=>{            const nextRootReducer=require('../reducers').default;            store.replaceReducer(nextRootReducer);        });    }    return store;}

module.hot实在启用了热跟新后才可以访问的。

index.js实现:

import 'babel-polyfill';import React from 'react';import {render} from 'react-dom';import {Provider} from 'react-redux';import App from './containers/App';import configureStore from './store/configureStore';const store=configureStore();render(    
,document.getElementById('root'));

server.js实现:

var webpack = require('webpack');var webpackDevMiddleware = require('webpack-dev-middleware');var webpackHotMiddleware = require('webpack-hot-middleware');var config = require('./webpack.config');var app= new (require('express'))();var port= 3000;var compiler = webpack(config);app.use(webpackDevMiddleware(compiler, {noInfo:true, publicPath:config.output.publicPath}));app.use(webpackHotMiddleware(compiler));app.get("/",function(req,res){    res.sendFile(__dirname+'/index.html');});app.listen(port,function(error){    if(error){        console.error(error);    }    else{        console.info("==> ?  Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port)    }});

package.json文件如下:

{  "name": "react-demo",  "version": "1.0.0",  "main": "index.js",  "scripts": {    "start":"node server.js"  },  "keywords": [],  "author": "",  "license": "ISC",  "devDependencies": {    "babel-core": "^6.26.3",    "babel-loader": "^7.1.5",    "babel-preset-es2015": "^6.24.1",    "babel-preset-react": "^6.24.1",    "babel-preset-react-hmre": "^1.1.1",    "expect": "^23.6.0",    "express": "^4.16.3",    "node-libs-browser": "^2.1.0",    "webpack": "^4.20.2",    "webpack-dev-middleware": "^3.4.0",    "webpack-hot-middleware": "^2.24.2"  },  "dependencies": {    "babel-polyfill": "^6.26.0",    "isomorphic-fetch": "^2.2.1",    "react": "^16.5.2",    "react-dom": "^16.5.2",    "react-redux": "^5.0.7",    "redux": "^4.0.0",    "redux-logger": "^3.0.6",    "redux-thunk": "^2.3.0"  }}

webpack.config.js:

var path = require("path");var webpack= require('webpack');module.exports = {    devtool:'cheap-module-eval-source-map',    entry: ['webpack-hot-middleware/client','./index.js'],    output : {        path:path.join(__dirname,'dist'),        filename:"bundle.js",        publicPath:'/static/'    },    module:{        rules:[            {                test:/\.js$/,                loaders:['babel-loader'] ,                exclude:'/node_modules/',                include:__dirname                                  }        ]    },    plugins:[        new webpack.optimize.OccurrenceOrderPlugin(),        new webpack.HotModuleReplacementPlugin()    ]}

index.html:

      Redux async example        

.babelrc:

{    "presets": [        "es2015","react"    ],    "env":{        "development":{            "presets":["react-hmre"]        }    }}

转载地址:http://sbszm.baihongyu.com/

你可能感兴趣的文章
Construct Binary Tree from Preorder and Inorder Traversal
查看>>
写得好 git 提交信息
查看>>
Linux下获取线程TID的方法
查看>>
Redis和Memcache的区别分析(转)
查看>>
网络请求 http get post 一
查看>>
《计算机问题求解》总结——2014年CCF计算机课程改革导教班(2014.07.11)
查看>>
Google Chrome Plus——绿色便携多功能谷歌浏览器
查看>>
Instant Run
查看>>
浏览器中 for in 反射 对象成员 的差异
查看>>
关于Linux启动时挂载rootfs的几种方式
查看>>
2018年总结
查看>>
34个漂亮的应用程序后台管理界面
查看>>
java JDK6的可变参数
查看>>
初入职场程序员的五大钻石法则
查看>>
Node.js学习笔记(一)概述
查看>>
split的3种方法
查看>>
忽略PNG透明区域的事件(AS/Flash)
查看>>
文本框只能输入正整数(大于0的整数)代码
查看>>
一步一个脚印学习WCF系列之WCF概要—WCF服务的创建与调用HelloWorld实例,通过配置文件方式(六)...
查看>>
只需简单一步,android自带的示例程序 BluetoothChat 变蓝牙串口助手
查看>>