我的第一个 react redux demo
最近学习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(
<span>
<h1>{value}</h1>
<select onChange={e=>onChange(e.target.value)} value={value}>
{options.map(o=>
<option value={o} key={o}>{o}</option>
)}
</select>
</span>
);
} 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(
<ul>
{
posts.map((p,i)=>
<li key={i}>{p.title}</li>
)
}
</ul>
);
} Posts.propTypes = {
posts:PropTypes.array.isRequired,
}; export default Posts;
- 现在来看看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 === ;
const message = isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>;
return(
<div>
<Picker value={selectedReddit} onChange={this.handleChange} options={['reactjs', 'frontend']} />
<p>
{ lastUpdated && <span>Last updated at {new Date(lastUpdated).toLocaleDateString()}.</span>}
{!isFetching && <a href="#" onClick={this.handleRefreshClick}>Refresh</a>}
</p>
{isEmpty?message:<div style={{opacity:isFetching?0.5:}}><Posts posts={posts}/></div>}
</div>
);
}
}
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(
<Provider store={store}>
<App />
</Provider>
,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= ; 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("==>我的第一个 react redux demo的更多相关文章
- 一个 React & Redux的目录树
|-----------------------------------------| | | | React & Redux | | | |------------------------- ...
- 一个 react 小的 demo
一.搭建开发环境: webpack构建工具. 新建一个文件夹(login),进入根目录, 1.输入命令:cnpm init,生成了一个package.json文件,这是一个标准的npm说明文件,里面蕴 ...
- React+Redux实现追书神器网页版
引言 由于现在做的react-native项目没有使用到redux等框架,写了一段时间想深入学习react,有个想法想做个demo练手下,那时候其实还没想好要做哪一个类型的,也看了些动漫的,小说阅读, ...
- Immutable.js 以及在 react+redux 项目中的实践
来自一位美团大牛的分享,相信可以帮助到你. 原文链接:https://juejin.im/post/5948985ea0bb9f006bed7472?utm_source=tuicool&ut ...
- react案例->新闻移动客户端--(react+redux+es6+webpack+es6的spa应用)
今天分享一个react应用,应在第一篇作品中说要做一个react+redux+xxx的应用.已经做完一部分,拿出来分享.github地址为:点我就可以咯~ 这里实现了一个新闻移动站的spa.本来想写p ...
- angular开发者吐槽react+redux的复杂:“一个demo证明你的开发效率低下”
曾经看到一篇文章,写的是jquery开发者吐槽angular的复杂.作为一个angular开发者,我来吐槽一下react+redux的复杂. 例子 为了让大家看得舒服,我用最简单的一个demo来展示r ...
- react+redux+generation-modation脚手架添加一个todolist
当我遇到问题: 要沉着冷静. 要管理好时间. 别被bug或error搞的不高兴,要高兴,又有煅炼思维的机会了. 要思考这是为什么? 要搞清楚问题的本质. 要探究问题,探究数据的流动. TodoList ...
- react+redux+generation-modation脚手架搭建一个todolist
TodoList 1. 编写actions.js 2. 分析state 试着拆分成多个reducer 3. 了解store 4. 了解redux数据流生命周期 5. 分析容器组件和展示组件 搞清楚,数 ...
- 使用react+redux+react-redux+react-router+axios+scss技术栈从0到1开发一个applist应用
先看效果图 github地址 github仓库 在线访问 初始化项目 #创建项目 create-react-app applist #如果没有安装create-react-app的话,先安装 npm ...
随机推荐
- pycharm中出现的错误
错误1 在pycharm上安装TensorFlow运行import tensorflow时报错: File , in _find_and_load SystemError: PyEval_EvalFr ...
- 今天才知道原来我还没弄清楚js中全局变量和局部变量的定义...
查资料看到这段还不错,来源:原文:https://blog.csdn.net/czh500/article/details/80429133 粘过来记录一下... 1.使用var声明变量,在方法内部是 ...
- java日志的相关配置文件知识
w2bc.com/article/261758”“”“
- 1、Qt Project之基本文件打开与保存
基本文件打开与保存: 首先是涉及到的头文件,我们需要在mainwindow.h包含头文件: #include <QFileDialog> #include <QFile> #i ...
- 【Java】idea找不到符号找不到类,但是却没有错误
编译错误,Ctrl+Shift+F9 将提示没有符号类的文件打开,右键单独编译一次,再重新打包即可解决: 特别说明:在Java的集成开发环境中,比如Eclipse.IDEA中,有常常有三种与编译相关的 ...
- Masquerade strikes back Gym - 101911D (数学)
Quite often the jury of Saratov SU use the problem "Masquerade" in different practice sess ...
- 牛客练习赛28B (经典)【线段树】
<题目链接> qn姐姐最好了~ qn姐姐给你了一个长度为n的序列还有m次操作让你玩, 1 l r 询问区间[l,r]内的元素和 2 l r 询问区间[l,r]内的元 ...
- POJ 1258 Agri-Net 【Prime】模板题
题目链接>>> 题目大意: 给你N*N矩阵,表示N个村庄之间的距离.FJ要把N个村庄全都连接起来,求连接的最短距离(即求最小生成树).解析如下: #include <c ...
- Unity容器的简单AOP与DI的应用Demo(基于asp.net mvc框架)
转发请注明出处:https://home.cnblogs.com/u/zhiyong-ITNote/ 整个Demo是基于Controller-Service-Repository架构设计的,每一层之间 ...
- phpmyadmin 修改执行时间
D:\xampp\phpMyAdmin\libraries\config.default.php $cfg['ExecTimeLimit'] = 0;