【React】React全栈脚手架搭建-苹果篮子示例

接着上一章,脚手架已经搭建完毕,接下来便可以编写组件,先上效果图:

 

对苹果篮子页面进行组件切割

根据视图,我们可以将其切割为两部分

AppleItem:里面的苹果列表项

AppleBasket:包着苹果列表项的篮子

解析苹果篮子的数据结构,应该有一个苹果列表 apples(每个苹果具有id,重量,是否被吃掉的标志),是否在采摘苹果状态标记等,如下

const basket = {
    isPicker:false,
    apples:[
      {
        id:1,
        weight:230,
        isEaten:false
      },
      {
        id:2,
        weight:120,
        isEaten:true
      },
      ......
    ]
}

看一下我们的项目结构:红色圈起来的是这次组件编写需要动到的部分

具体组件如何编写,直接贴出代码

AppleItem:这是一个用于展示苹果的列表项,props只需传递进来具体一个苹果的数据apple和吃掉苹果的处理函数eatApple

import React from 'react';
import './index.less';
import '../../styles/common.less';
import appleImg from '../../assets/apple.png'
class AppleItem extends React.Component {
  render(){
    let {apple,eatApple} = this.props;
    return(
      <div className="apple-item row">
        <div className='row'>
          <div className="apple"><img src={appleImg} alt="苹果图片"/></div>
          <div className="info">
            <div className="name">红苹果 - {apple.id}号</div>
            <div className="weight">{apple.weight}克</div>
          </div>
        </div>
        <div className="btn-div"><button onClick={eatApple.bind(this,apple.id)}>吃掉</button></div>
      </div>
    )
  }
}
export default AppleItem;

 AppleBasket:这个稍微复杂点,但是也没那么复杂。props接收外界传递进来的就是整个苹果篮子的数据,以及所有的苹果处理函数的集合

拿到 整个苹果篮子的数据appleBasket,跟视图的数据结构是不符合,所以就需要将其转化为我们视图所需要需要的数据结构。map遍历appleBasket.apples,统计已吃跟未吃的苹果数量,重量,将未吃苹果push进新数组。未知苹果的数组就是我们要的数据。

import React from 'react';
import './index.less';
import '../../styles/common.less'
import AppleItem from '../AppleItem';


class AppleBasket extends React.Component {
  render(){
    const {appleBasket,actions} = this.props;
    let stats = {
      isPicker:appleBasket.isPicker,
      eat:{num:0,weight:0},
      noeat:{num:0,weight:0},
      apples:[]
    }
    appleBasket.apples.map(elem => {
      let name = elem.isEaten?"eat":"noeat";
      stats[name].num++;
      stats[name].weight += elem.weight;
      if (!elem.isEaten) {
        stats.apples.push(elem);
      }
    })
    function getNoApple(){
      if (stats.apples.length===0 && !stats.isPicker) {
        return <div className='no-apple'>篮子空空如也,快去摘苹果吧</div>
      }
      return;
    }
    const that = this;
    function getApples(){
      let data = [];
      if (!stats.isPicker) {
        data.push(stats.apples.map((apple,index) => <AppleItem key={index} apple={apple} eatApple={that.props.actions.eatApple}/> ))
      }else{
        return <div className='no-apple'>正在采摘苹果...</div>
      }
      return data;
    }
    return(
      <div className="apple-basket">
        <div className="title">苹果篮子</div>
        <div className="stats row">
          <div className='col current'>
            <div className="label">当前</div>
            <div><span>{stats.noeat.num}</span>个苹果,<span>{stats.noeat.weight}</span>克</div>
          </div>
          <div className='col eat'>
            <div className="label">已吃掉</div>
            <div><span>{stats.eat.num}</span>个苹果,<span>{stats.eat.weight}</span>克</div>
          </div>
        </div>
        <div className="apple-list col">
          {getApples()}
          {getNoApple()}
        </div>
        <div className="btn-panel row"><button className={stats.isPicker ? 'disabled' : ''} onClick={actions.pickApple}>摘苹果</button></div>
      </div>
    )
  }
}
export default AppleBasket;

 

组件的编写就完成了。写完组件,可能有人会疑问,那我的props数据从哪里来,我怎么展示我已经写好的组件,这时候就需要我们写一个容器级(或页面级)组件,然后将应用的state作为props传递进去给子组件,当然,这些都是后话。就算没有这些操作,我们有storybook,这是一个可以对组件进行单元测试的有利工具。

启动storybook:npm run storybook

打开 http://localhost:9009便可以看到页面

要想在storybook看到我们编写的组件就需要在 stories/index文件里面add我们的组件

语法十分简单,模仿storybook提供的demo(Welcome/Button),照猫画虎,很容易就可以引入我们的组件

storiesOf('Apple',module)
  .add("AppleItem",()=>(<AppleItem apple={apple} eatApple={action("eatApple")}/>))
  .add("AppleBasket",()=>(<AppleBasket appleBasket={basket} actions={appleActions}/>))

需要传递给组件的props数据需要我们事先编写,可以理解为我们的测试数据。最终完整的引入如下

import AppleItem from '../src/components/AppleItem/index'
import AppleBasket from '../src/components/AppleBasket/index'

const basket = {
    isPicker:false,
    apples:[
      {
        id:1,
        weight:230,
        isEaten:false
      },
      {
        id:2,
        weight:120,
        isEaten:true
      },
      {
        id:3,
        weight:290,
        isEaten:false
      },
      {
        id:4,
        weight:118,
        isEaten:false
      },
      {
        id:5,
        weight:280,
        isEaten:true
      }
    ]
  }
const apple = {id:3,weight:280,isEaten:false};
const appleActions = {
  eatApple: (id) => action("eatApple")(id),
  pickApple: () => action("pickApple")('摘苹果')
};
storiesOf('Apple',module)
  .add("AppleItem",()=>(<AppleItem apple={apple} eatApple={action("eatApple")}/>))
  .add("AppleBasket",()=>(<AppleBasket appleBasket={basket} actions={appleActions}/>))

 

 

组件的编写便告一段落了

接下来就是如何在我们的页面里面展示苹果篮子实例以及数据处理 redux

 

2. 苹果篮子 redux处理

2.1 整个应用就是store,而应用数据的来源就是store里面的state,redux的作用就是处理state。redux主要分为两部分:

actions:actions分同步action跟异步action。同步action返回一个对象,异步action返回一个函数,在函数里面进行异步请求

reducers:根据actions类型的不同,分别进入到不同的处理函数,返回新的state

用户不能直接修改数据,只能触发通过触发action来修改数据,action就是一个定义好的普通对象,type表示动作类型,payload用来负载用户触发action携带的数据。

{

  type:'PICK_APPLE',

  payload:445

}

2.2 actions

苹果篮子实例,分别有吃苹果,摘苹果的动作,对应有 eatApple / pickApple 的action,还需注意有苹果数据初始化的隐藏action

在这里我将 pickApple作为异步处理,即摘到的苹果数据由后台返回,将eatApple做同步处理。这样通过比较同步和异步action就能很清楚的知道两者的区别。

同步action直接返回对象即可。但是异步action需要进行action切割:通知异步开始的action,异步成功的action,异步失败的action。

最终代码如下

import Apples from '../services';  //Apples 是已经封装好的异步请求接口

let actions = {
  initApplesStart:function(){
    return function(dispatch,getState){
      Apples.init().then(function(res){
        dispatch(actions.initApplesSuccess(res));
      }, function(err){
        dispatch(actions.initApplesFail(err));
      });
    }
  },
  initApplesSuccess:(data)=>({
    type:'apple/INIT_APPLE_SUCCESS',
    payload:data
  }),
  initApplesFail:(data)=>({
    type:'apple/INIT_APPLE_FAIL',
    payload:data
  }),
  eatApple:(id) => ({
    type:'apple/EAT_APPLE',
    payload:id
  }),
  pickApple:function(){
    return function(dispatch,getState){
      if(getState().appleBasket.isPicker){
        return;
      }
      dispatch(actions.beginPickApple());
      Apples.pick().then(function(res){
        dispatch(actions.donePickApple(res.apples));
      }, function(err){
        dispatch(actions.donePickApple(err));
      });
    }
  },
  beginPickApple:() => ({
    type:'apple/BEGIN_PICK_APPLE'
  }),
  donePickApple:appleWeight => ({
    type:'apple/DONE_PICK_APPLE',
    payload:appleWeight
  }),
  failPickApple:err => ({
    type:'apple/FAIL_PICK_APPLE',
    payload:err
  })
}
export default actions;

2.3 reducers

reducers相对来说,简单点,其接收action,通过判断action类型的不同对state做不同的处理,然后返回新的state即可

const initialState = {
  isPicker:false,
  apples:[]
};

const appleReducer = (state=initialState,action) =>{
  switch (action.type) {

    case 'apple/INIT_APPLE_SUCCESS':
      state = action.payload;
      return {...state};

    case 'apple/INIT_APPLE_FAIL':
      return state;

    case 'apple/EAT_APPLE':
      state.apples.map(elem => {
        if (elem.id===action.payload) {
          elem.isEaten = true;
        }
      })
      return {...state};

    case 'apple/BEGIN_PICK_APPLE':
      state.isPicker = true;
      return {...state};

    case 'apple/DONE_PICK_APPLE':
      state.isPicker = false;
      state.apples.push(...action.payload);
      return {...state};

    case 'apple/FAIL_PICK_APPLE':
      state.isPicker = false;
      return {...state};

    default:
      return state;
  }
}
export default appleReducer;

2.4 通常我们的应用不止一个 reducers,这时候需要将reducers进行整合。

import { combineReducers } from 'redux';
import appleReducer from './appleReducer';
//import todoReducers from './todoReducers';
const rootReducer = combineReducers({
   // todoItems:todoReducers,
    appleBasket: appleReducer
});

export default rootReducer;

2.5 store

有了 reducers就可以创建 store了

在入口文件 src/index.js里createStore,然后将store注入我们的应用里面即可

import React from 'react';
import ReactDOM from 'react-dom';
import {createStore,applyMiddleware} from 'redux';
import thunk from 'redux-thunk';

import registerServiceWorker from './registerServiceWorker';
import reducer from './redux/reducers'
import Root from './pages/Root'

let store = createStore(reducer,applyMiddleware(thunk))

ReactDOM.render(
    <Root store={store}/>,
    document.getElementById('root')
);
registerServiceWorker();

2.6 编写页面级组件 Apples.js

通过 redux的connect函数可以将state映射到props,将actions和dispatch映射到props

import React from 'react'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import AppleBasket from '../components/AppleBasket/index';
import actions from '../redux/actions/appleAction';

class Apples extends React.Component {
  constructor(props){  //初始化数据
    super(props);
    this.props.dispatch(this.props.actions.initApplesStart)
  }
  render(){
    let {appleBasket,actions,dispatch} = this.props;
    return(
      <div className="apples">
        <AppleBasket appleBasket={appleBasket} actions={actions}/>
      </div>
    )
  }
}

const mapStateToProps = state => ({
    appleBasket: state.appleBasket
});
const mapDispatchToProps = dispatch => ({
    dispatch: dispatch,
    actions: bindActionCreators(actions, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(Apples);

 

2.7 编写项目根组件 root

import React from 'react';
import {Provider} from 'react-redux';

import '../styles/common.less'
import Apples from '../pages/Apples'

export default class Root extends React.Component{
  render(){
    const { store } = this.props;
    return(
      <Provider store={store}>
        <Apples/>
      </Provider>
    )
  }
}

 

最终 npm run start

可以在 localhost:3000看到苹果篮子实例成功跑起来了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值