11_Workoutlogger ---ReactJS and Flux: Learn By Building 10 Projects

本文介绍了一个基于React和Flux的单页应用项目示例——健身记录器。该项目使用客户端渲染,通过Gulp进行构建和监控,并利用React处理UI组件,Flux管理应用状态。文章详细展示了项目结构、依赖配置及关键代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习编程的最好方法就是去编程。

这篇笔记项目来自于:
https://www.udemy.com/reactjs-and-flux-learn-by-building-10-projects/ 的第11课。

最终效果如图:
workoutLogger App

这个项目是单页面应用,使用的是客户端的渲染,没有用到服务器。
package.json

{
  "name": "workoutlogger",
  "version": "1.0.0",
  "description": "Workout Logger",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Morphues Cao",
  "license": "ISC",
  "devDependencies":{
    "browserify":"*",
    "gulp":"*",
    "reactify":"*",
    "vinyl-source-stream":"*",
    "object-assign":"*"
  },
  "dependencies":{
    "react":"^0.14.7",
    "react-dom":"^0.14.7",
    "flux":"^2.1.1"
  }
}

使用gulp进行编译,复制和监控。gulpfile.js

//path %PATH%;.\node_modules\.bin

var gulp = require('gulp');
var browserify = require('browserify');
var reactify = require('reactify');
var source = require('vinyl-source-stream');

gulp.task('browserify',function(){
    browserify('./src/js/main.js')
        .transform('reactify')      //jsx -> js
        .bundle()
        .pipe(source('main.js'))
        .pipe(gulp.dest('dist/js'));
});

gulp.task('copy',function(){
    gulp.src('src/index.html')
        .pipe(gulp.dest('dist'));
    gulp.src('src/css/*.*')
        .pipe(gulp.dest('dist/css'));
    gulp.src('src/js/vendors/*.*')
        .pipe(gulp.dest('dist/js'));
});

gulp.task('default',['browserify','copy'],function(){
    return gulp.watch('src/**/*.*',['browserify','copy']);
});

目录结构:
项目目录树
参考flux的unidirectional data flow.:
这里写图片描述
对应的文件结构就更容易理解:
index.html

<html>
    <head>
        <title>Workout logger</title>
        <link rel="stylesheet" href="css/bootstrap.css"/>
        <link rel="stylesheet" href="css/style.css"/>
    </head>
    <body>
        <div class="row">
            <div class="col-md-4 col-md-offset-4">
                <div id="app">TEST APP</div>
            </div>
        </div>

        <script src="js/jquery.js"></script>
        <script src="js/main.js"></script>
    </body>
</html>

js/main.js

var App = require('./components/App');
var React = require('react');
var ReactDOM = require('react-dom');
var AppAPI = require('./utils/AppAPI.js')
var StartData = require('./startData.js');

if(localStorage.getItem('workouts') == null){
    StartData.init();
}

AppAPI.getWorkouts();

ReactDOM.render(
    <App />,
    document.getElementById('app')
);

js/components/App.js

var React = require('react');
var AppActions = require('../actions/AppActions');
var AppStore = require('../stores/AppStore');
var AddForm = require('./AddForm.js');
var Workouts = require('./Workouts.js');

function getAppState(){
    return {
        showForm: AppStore.getShowForm(),
        workouts: AppStore.getWorkouts()
    }
}

var App = React.createClass({
    getInitialState: function(){
        return getAppState();
    },

    componentDidMount:function(){
        AppStore.addChangeListener(this._onChange);
    },
    componentWillUnmount:function(){
        AppStore.removeChangeListener(this._onChange)
    },

    onShowFormClick:function(e){
        e.preventDefault();
        AppActions.showForm();
    },

    render: function(){
        console.log(this.state.workouts)
        if(this.state.showForm){
            var form = <AddForm />
        }else{
             var form = '';
        }
        return (
            <div>
                <h1 className="text-center page-header">WorkoutLogger</h1>
                <a onClick={this.onShowFormClick} href="#" className="btn btn-primary btn-block">Add Workout</a>
                <br/>
                {form}
                <br/>
                <Workouts workouts = {this.state.workouts} />
                <br/>
            </div>
        )
    },

    _onChange: function(){
        this.setState(getAppState());
    }
});

module.exports = App;

js/components/AddForm.js

var React = require('react');
var AppActions = require('../actions/AppActions');
var AppStore = require('../stores/AppStore');

var AddForm = React.createClass({

    render: function(){
        return (
            <form onSubmit={this.onSubmit}>
                <div className="form-group">
                    <select className="form-control" ref="type">
                        <option value="Jogging">Jogging</option>
                        <option value="Weight Lifting">Weight Lifting</option>
                        <option value="Elliptical">Elliptical</option>
                        <option value="Yoga">Yoga</option>
                        <option value="Other">Other</option>
                    </select>
                </div>
                <div className="form-group">    
                    <input type="text" className="form-control"
                        ref="minutes" placeholder="Minutes"/>
                </div>
                <div className="form-group">    
                    <input type="text" className="form-control"
                        ref="miles" placeholder="Miles(Optional)"/>
                </div>
                <button type="submit" className="btn btn-default btn-block">Log Workout</button>
            </form>
        )
    },

    onSubmit: function(e){
        e.preventDefault();

        var workout = {
            id: this.generateId(),
            type:this.refs.type.value.trim(),
            minutes:this.refs.minutes.value.trim(),
            miles:this.refs.miles.value.trim(),
            date:new Date()
        }
        //console.log(workout);
        AppActions.addWorkout(workout);
    },

    generateId: function(){
        var id='';
        var possible = '0123456789';

        for(var i=0;i<5;i++){
            id += possible.charAt(Math.floor(Math.random() * possible.length));
        }

        return id;
    }
});

module.exports = AddForm;

js/components/Workouts.js

var React = require('react');
var AppActions = require('../actions/AppActions');
var AppStore = require('../stores/AppStore');
var Workout = require('./Workout');

var Workouts = React.createClass({
    render: function(){
        return (
            <ul className="list-group">
                {
                    this.props.workouts.map(function(workout,i){
                        // console.log(workout);
                        return(
                            <Workout workout={workout} key={i} />
                        )
                    })
                }
            </ul>
        )
    }
});

module.exports = Workouts;

js/components/Workout.js

var React = require('react');
var AppActions = require('../actions/AppActions');
var AppStore = require('../stores/AppStore');


var Workout = React.createClass({
    render: function(){
        if(this.props.workout.miles != ''){
            var miles = ' | '+this.props.workout.miles + 'Miles';
        }else{
            var miles = '';
        }
        return (
            <li className="list-group-item">
                 {this.props.workout.type} - {this.props.workout.minutes} Minutes {miles}
                    <a href="#" onClick={this.onDelete.bind(this,this.props.workout.id)}
                     className="delete">X</a>
            </li>
        )
    },

    onDelete:function(i,j){
        AppActions.removeWorkout(i);
    }
});

module.exports = Workout;

js/actions/AppActions.js

var AppDispatcher = require('../dispatcher/AppDispatcher');
var AppConstants = require('../constants/AppConstants');

var AppActions = {
    showForm: function(){
        // console.log('AppAction: Searching for movie' + movie.title);
        AppDispatcher.handleViewAction({
            actionType: AppConstants.SHOW_FORM
        });
    },
    addWorkout:function(workout){
        AppDispatcher.handleViewAction({
            actionType: AppConstants.ADD_WORKOUT,
            workout:workout
        });
    },
    receiveWorkouts:function(workouts){
        AppDispatcher.handleViewAction({
            actionType: AppConstants.RECEIVE_WORKOUTS,
            workouts:workouts
        });
    },
    removeWorkout:function(workoutId){
        AppDispatcher.handleViewAction({
            actionType: AppConstants.REMOVE_WORKOUT,
            workoutId:workoutId
        });
    }
}

module.exports = AppActions;

js/dispatcher/AppDispatcher.js

var Dispatcher = require('flux').Dispatcher;
var assign = require('object-assign');

var AppDispatcher = assign(new Dispatcher(),{
    handleViewAction: function(action){
        var payload = {
            source:"VIEW_ACTION",
            action: action
        }
        this.dispatch(payload);
    }
});

module.exports = AppDispatcher;

js/stores/AppStore.js

var AppDispatcher = require('../dispatcher/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var AppAPI = require('../utils/AppAPI.js')

var CHANGE_EVENT = 'change';

var _workouts = [];
var _showForm = false;

var AppStore = assign({},EventEmitter.prototype,{
    showForm: function(){
        _showForm = true;
    },
    getShowForm: function(){
        return _showForm;
    },
    addWorkout:function(workout){
        _workouts.push(workout);
    },
    getWorkouts:function(){
        return _workouts;
    },
    receiveWorkouts:function(workouts){
        _workouts = workouts;
    },
    removeWorkout:function(workoutId){
        // var index =_workouts.findIndex(x => x.id === workoutId);
        function findIndex(_workouts,workoutId){
            for(var i=0,len=_workouts.length;i<len;i++){
                if(_workouts[i]._id === workoutId){
                    return i;
                }
            }
            return -1;
        }
        var index = findIndex(_workouts,workoutId);
        _workouts.splice(index,1);
    },
    emitChange:function(){
        this.emit(CHANGE_EVENT);
    },
    addChangeListener: function(callback){
        this.on('change',callback);
    },
    removeChangeListener:function(callback){
        this.removeListener('change',callback);
    }
});

AppDispatcher.register(function(payload){
    var action = payload.action;

    switch(action.actionType){
        case AppConstants.SHOW_FORM:
            AppStore.showForm();
            AppStore.emit(CHANGE_EVENT);
            break;
        case AppConstants.ADD_WORKOUT:
            AppStore.addWorkout(action.workout);
            AppAPI.addWorkout(action.workout);
            AppStore.emit(CHANGE_EVENT);
            break;
        case AppConstants.RECEIVE_WORKOUTS:
            AppStore.receiveWorkouts(action.workouts);
            AppStore.emit(CHANGE_EVENT);
            break;
        case AppConstants.REMOVE_WORKOUT:
            AppStore.removeWorkout(action.workoutId);
            AppAPI.removeWorkout(action.workoutId);
            AppStore.emit(CHANGE_EVENT);
            break;
    }

    return true;
})

module.exports = AppStore;

js/utils/AppAPI.js

var AppActions = require('../actions/AppActions');

module.exports = {
    addWorkout: function(workout){
        console.log('Saving Workout...');
        var workouts = JSON.parse(localStorage.getItem('workouts'));
        workouts.push(workout);
        console.log('addWorkout:',workouts);
        localStorage.setItem('workouts',JSON.stringify(workouts));
    },
    getWorkouts:function(){
        var workouts = JSON.parse(localStorage.getItem('workouts'));
        AppActions.receiveWorkouts(workouts);
    },
    removeWorkout:function(workoutId){
        var workouts = JSON.parse(localStorage.getItem('workouts'));
        for(var i=0;i<workouts.length;i++){
            if(workouts[i].id ==workoutId){
                workouts.splice(i,1);
            }
        }
        localStorage.setItem('workouts',JSON.stringify(workouts));
    }
}

js/startData.js

module.exports ={
    init:function(){
        localStorage.clear();
        localStorage.setItem('workouts',JSON.stringify([
        {
            id:0001,
            type:'Jogging',
            minutes:20,
            miles:2,
            date: new Date()
        },
        {
            id:0002,
            type:'Yoga',
            minute:40,
            miles:'',
            date:new Date()
        }
        ]));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值