
learn from 《React全家桶:前端开发与实例详解》 https://zh-hans.reactjs.org/tutorial/tutorial.html https://zh-hans.reactjs.org/docs/create-a-new-react-app.html#create-react-app
本章学习的目标是做一个定时器


app.js
class TimersDashBoard extends React.Component {
    render() {
        return (
            <div className='ui three column centered grid'>
                <div className='column'>
                    <EditableTimerList/>
                    <ToggleableTimerForm isOpen={true}/>
                </div>
            </div>
        )
    }
}
class EditableTimerList extends React.Component {
    render() {
        return (
            <div id='timers'>
                <EditableTimer
                    title='学习react'
                    project='react web'
                    elasped='3600'
                    runningSince={null}
                    editFormOpen={false}
                />
                <EditableTimer
                    title='学习python'
                    project='deep learning'
                    elasped='3600'
                    runningSince={null}
                    editFormOpen={true}
                />
            </div>
        )
    }
}
class EditableTimer extends React.Component {
    render() {
        if (this.props.editFormOpen) {
            return (
                <TimerForm
                    title={this.props.title}
                    project={this.props.project}
                />
            )
        } else {
            return (
                <Timer
                    title={this.props.title}
                    project={this.props.project}
                    elasped={this.props.elasped}
                    runningSince={this.props.runningSince}
                />
            )
        }
    }
}
class TimerForm extends React.Component {
    render() {
        const submitText = this.props.title ? 'Update' : 'Create'
        return (
            <div className='ui centered card'>
                <div className='content'>
                    <div className='ui form'>
                        <div className='field'>
                            <label>Title</label>
                            <input type='text' defaultValue={this.props.title}/>
                        </div>
                        <div className='field'>
                            <label>Project</label>
                            <input type='text' defaultValue={this.props.project}/>
                        </div>
                        <div className='ui two bottom attached buttons'>
                            <button className='ui basic blue button'>
                                {submitText}
                            </button>
                            <button className='ui basic red button'>
                                Cancel
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}
class ToggleableTimerForm extends React.Component {
    render() {
        if (this.props.isOpen) {
            return (
                <TimerForm/>
            )
        } else {
            return (
                <div className='ui basic content center aligned segment'>
                    <button className='ui basic button icon'>
                        <i className='plus icon'/>
                    </button>
                </div>
            )
        }
    }
}
class Timer extends React.Component {
    render() {
        const elaspedString = helpers.renderElapsedString(this.props.elasped)
        return (
            <div className='ui centered card'>
                <div className='content'>
                    <div className='header'>
                        {this.props.title}
                    </div>
                    <div className='meta'>
                        {this.props.project}
                    </div>
                    <div className='center aligned description'>
                        <h2> {elaspedString} </h2>
                    </div>
                    <div className='extra content'>
                        <span className='right floated edit icon'>
                            <i className='edit icon'/>
                        </span>
                        <span className='right floated trash icon'>
                            <i className='trash icon'/>
                        </span>
                    </div>
                </div>
                <div className='ui bottom attached blue basic button'>
                    Start
                </div>
            </div>
        )
    }
}
ReactDOM.render(
    <TimersDashBoard/>,
    document.getElementById('content')
)
 把 <ToggleableTimerForm isOpen={true}/> 改成 false, ToggleableTimerForm 的 render 回返回 显示 plus icon 的分支
 

原则就是用尽可能少的 state
如何确定,步骤:
props 可理解为 : 不可改变的 state
class TimersDashBoard extends React.Component {
    state = {
        timers: [
            {
                title: 'react learning',
                project: 'web dev',
                id: uuid.v4(),
                elasped: 3600 * 1000,
                runningSince: Date.now(),
            },
            {
                title: 'python learning',
                project: 'ai dev',
                id: uuid.v4(),
                elasped: 30 * 1000,
                runningSince: null,
            }
        ]
    }
    render() {
        return (
            <div className='ui three column centered grid'>
                <div className='column'>
                    <EditableTimerList
                        timers={this.state.timers}
                    />
                    <ToggleableTimerForm isOpen={false}/>
                </div>
            </div>
        )
    }
}
class EditableTimerList extends React.Component {
    render() {
        const timers = this.props.timers.map(
            (timer) => (
                <EditableTimer
                    key={timer.id}
                    id={timer.id}
                    title={timer.title}
                    project={timer.project}
                    elasped={timer.elasped}
                    runningSince={timer.runningSince}
                />
            )
        )
        return (
            <div id='timers'>
                {timers}
            </div>
        )
    }
}class EditableTimer extends React.Component {
    state = {
        editFormOpen: false,
    }
    render() {
        if (this.state.editFormOpen) {
            return (
                <TimerForm
                    id={this.props.id}
                    title={this.props.title}
                    project={this.props.project}
                />
            )
        } else {
            return (
                <Timer
                    id={this.props.id}
                    title={this.props.title}
                    project={this.props.project}
                    elasped={this.props.elasped}
                    runningSince={this.props.runningSince}
                />
            )
        }
    }
}
class TimerForm extends React.Component {
    state = {
        title: this.props.title || '',
        project: this.props.project || '',
    }
    handleTitleChange = (e) => {
        this.setState({title: e.target.value})
    }
    handleProjectChange = (e) => {
        this.setState({project: e.target.value})
    }
    render() {
        const submitText = this.props.title ? 'Update' : 'Create'
        return (
            <div className='ui centered card'>
                <div className='content'>
                    <div className='ui form'>
                        <div className='field'>
                            <label>Title</label>
                            <input type='text' value={this.state.title}
                                onChange={this.handleTitleChange}/>
                        </div>
                        <div className='field'>
                            <label>Project</label>
                            <input type='text' value={this.state.project}
                                   onChange={this.handleProjectChange}/>
                        </div>
                        <div className='ui two bottom attached buttons'>
                            <button className='ui basic blue button'>
                                {submitText}
                            </button>
                            <button className='ui basic red button'>
                                Cancel
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}
class ToggleableTimerForm extends React.Component {
    state = {
        isOpen: false,
    }
    handleFormOpen = () => {
        this.setState({isOpen: true});
    }
    render() {
        if (this.state.isOpen) {
            return (
                <TimerForm/>
            )
        } else {
            return (
                <div className='ui basic content center aligned segment'>
                    <button
                        className='ui basic button icon'
                        onClick={this.handleFormOpen}>
                        <i className='plus icon'/>
                    </button>
                </div>
            )
        }
    }
}TimerForm组件
让父组件拥有函数(在事件发生时决定采取什么行为),父组件 通过 props 将函数传递给 TimerForm
class TimerForm extends React.Component {
    state = {
        title: this.props.title || '',
        project: this.props.project || '',
    }
    handleTitleChange = (e) => {
        this.setState({title: e.target.value})
    }
    handleProjectChange = (e) => {
        this.setState({project: e.target.value})
    }
    handleSubmit = () => {
        this.props.onFormSubmit({
            id: this.props.id,
            title: this.state.title,
            project: this.state.project,
        })
    }
    render() {
        const submitText = this.props.id ? 'Update' : 'Create'
        return (
            <div className='ui centered card'>
                <div className='content'>
                    <div className='ui form'>
                        <div className='field'>
                            <label>Title</label>
                            <input type='text' value={this.state.title}
                                onChange={this.handleTitleChange}/>
                        </div>
                        <div className='field'>
                            <label>Project</label>
                            <input type='text' value={this.state.project}
                                   onChange={this.handleProjectChange}/>
                        </div>
                        <div className='ui two bottom attached buttons'>
                            <button className='ui basic blue button'
                                    onClick={this.handleSubmit}>
                                {submitText}
                            </button>
                            <button className='ui basic red button'
                                    onClick={this.props.onFormClose}>
                                Cancel
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}class ToggleableTimerForm extends React.Component {
    state = {
        isOpen: false,
    }
    handleFormOpen = () => {
        this.setState({isOpen: true});
    }
    handleFormClose = () => {
        this.setState({isOpen: false});
    }
    handleFormSubmit = (timer) => {
        this.props.onFormSubmit(timer);
        this.setState({
            isOpen: false,
        })
    }
    render() {
        if (this.state.isOpen) {
            return (
                <TimerForm
                    onFormSubmit={this.handleFormSubmit}
                    onFormClose={this.handleFormClose}
                />
            )
        } else {
            return (
                <div className='ui basic content center aligned segment'>
                    <button
                        className='ui basic button icon'
                        onClick={this.handleFormOpen}>
                        <i className='plus icon'/>
                    </button>
                </div>
            )
        }
    }
}	handleCreateFormSubmit = (timer) => {
        this.createTimer(timer);
    }
    createTimer = (timer) => {
        const t = helpers.newTimer(timer);
        this.setState({
            timers: this.state.timers.concat(t),
        })
    }
	render() {
	        return (
	            <div className='ui three column centered grid'>
	                <div className='column'>
	                    <EditableTimerList
	                        timers={this.state.timers}/>
	                    <ToggleableTimerForm
	                        onFormSubmit={this.handleCreateFormSubmit}/>
	                </div>
	            </div>
	        )
	    }

还不能开始,删除,修改
						<span
                            className='right floated edit icon'
                            onclick={this.props.onEditClick}>
                            <i className='edit icon'/>
                        </span>	state = {
        editFormOpen: false,
    }
    handleEditClick = () => {
        this.openForm()
    }
    handleFormClose = () => {
        this.closeForm()
    }
    handleSubmit = (timer) => {
        this.props.onFormSubmit(timer);
        this.closeForm();
    }
    closeForm = () => {
        this.setState({editFormOpen: false});
    }
    openForm = () => {
        this.setState({editFormOpen: true});
    }这些函数作为 props 向下传递
	render() {
        if (this.state.editFormOpen) {
            return (
                <TimerForm
                    id={this.props.id}
                    title={this.props.title}
                    project={this.props.project}
                    onFormSubmit={this.handleFormSubmit}
                    onFormClose={this.handleFormClose}
                />
            )
        } else {
            return (
                <Timer
                    id={this.props.id}
                    title={this.props.title}
                    project={this.props.project}
                    elapsed={this.props.elapsed}
                    runningSince={this.props.runningSince}
                    onEditClick={this.handleEditClick}
                />
            )
        }
    }onFormSubmit={this.props.onFormSubmit}class EditableTimerList extends React.Component {
    render() {
        const timers = this.props.timers.map(
            (timer) => (
                <EditableTimer
                    key={timer.id}
                    id={timer.id}
                    title={timer.title}
                    project={timer.project}
                    elapsed={timer.elapsed}
                    runningSince={timer.runningSince}
                    onFormSubmit={this.props.onFormSubmit}
                />
            )
        )
        return (
            <div id='timers'>
                {timers}
            </div>
        )
    }
}	handleEditFormSubmit = (attrs) => {
        this.updateTimer(attrs);
    }
    updateTimer = (attrs) => {
        this.setState({
            timers: this.state.timers.map((timer) => {
                if (timer.id === attrs.id) {
                    return Object.assign({}, timer,{
                        title: attrs.title,
                        project: attrs.project,
                    })
                }
                else{
                    return timer;
                }
            })
        })
    }属性传递给 EditableTimerList
					<EditableTimerList
                        timers={this.state.timers}
                        onFormSubmit={this.handleEditFormSubmit}
                    />删除计时器
Timer
	handleTrashClick = () => {
        this.props.onTrashClick(this.props.id)
    }
	。。。
						<span className='right floated trash icon'
                            onClick={this.this.handleTrashClick}>
                            <i className='trash icon'/>
                        </span>向上传递 EditableTimer
		else {
            return (
                <Timer
                    id={this.props.id}
                    title={this.props.title}
                    project={this.props.project}
                    elapsed={this.props.elapsed}
                    runningSince={this.props.runningSince}
                    onEditClick={this.handleEditClick}
                    onTrashClick={this.props.onTrashClick} // add
                />
            )
        }向上传递 EditableTimerList
			<EditableTimer
                    key={timer.id}
                    id={timer.id}
                    title={timer.title}
                    project={timer.project}
                    elapsed={timer.elapsed}
                    runningSince={timer.runningSince}
                    onFormSubmit={this.props.onFormSubmit}
                    onTrashClick={this.props.onTrashClick}  // add
                />TimersDashBoard 顶层实现这个删除函数
	handleTrashClick = (timerId) => {
        this.deleteTimer(timerId);
    }
    deleteTimer = (timerId) => {
        this.setState({
            timers: this.state.timers.filter(t => t.id !== timerId)
        })
    }
    。。。
					<EditableTimerList
                        timers={this.state.timers}
                        onFormSubmit={this.handleEditFormSubmit}
                        onTrashClick={this.handleTrashClick} // add
                    />
Timer组件
	componentDidMount() {
        this.forceUpdateInterval = setInterval(() => this.forceUpdate(), 50)
    } // 每 50 ms 调用一次 forceUpdate,重新渲染
    componentWillUnmount() {
        clearInterval(this.forceUpdateInterval)
    }  // 停止 forceUpdateInterval 的间隔执行,计时器删除之前调用
    handleTrashClick = () => {
        this.props.onTrashClick(this.props.id)
    }
    render() {
        const elapsedString = helpers.renderElapsedString(this.props.elapsed, this.props.runningSince)
        return (
        。。。
        )
    }添加启动,停止
	handleStartClick = () => {
        this.props.onStartClick(this.props.id)
    }
    handleStopClick = () => {
        this.props.onStopClick(this.props.id)
    }在 Timer 的 return 最底部添加 新组件
				<TimerActionButton
                    timerIsRunning={!!this.props.runningSince}
                    onStartClick={this.handleStartClick}
                    onStopClick={this.handleStopClick}
                />TimerActionButtonclass TimerActionButton extends React.Component {
    render() {
        if (this.props.timerIsRunning) {
            return (
                <div
                    className='ui bottom attached red basic button'
                    onClick={this.props.onStopClick}
                >
                    Stop
                </div>
            )
        } else {
            return (
                <div
                    className='ui bottom attached green basic button'
                    onClick={this.props.onStartClick}
                >
                    Start
                </div>
            )
        }
    }
}这些事件需要在上层结构中传递
EditableTimer
				<Timer
                    id={this.props.id}
                    title={this.props.title}
                    project={this.props.project}
                    elapsed={this.props.elapsed}
                    runningSince={this.props.runningSince}
                    onEditClick={this.handleEditClick}
                    onTrashClick={this.props.onTrashClick}
                    onStartClick={this.props.onStartClick} // add
                    onStopClick={this.props.onStopClick} // add
                />EditableTimerList
				<EditableTimer
                    key={timer.id}
                    id={timer.id}
                    title={timer.title}
                    project={timer.project}
                    elapsed={timer.elapsed}
                    runningSince={timer.runningSince}
                    onFormSubmit={this.props.onFormSubmit}
                    onTrashClick={this.props.onTrashClick}
                    onStartClick={this.props.onStartClick} // add
                    onStopClick={this.props.onStopClick} // add
                />TimersDashBoard
	handleStartClick = (timerId) => {
        this.startTimer(timerId);
    }
    startTimer = (timerId) => {
        const now = Date.now();
        this.setState({
            timers: this.state.timers.map((timer) => {
                if(timer.id === timerId) {
                    return Object.assign({}, timer, {
                        runningSince: now
                    })
                }else{
                    return timer;
                }
            })
        })
    }
    handleStopClick = (timerId) => {
        this.stopTimer(timerId);
    }
    stopTimer = (timerId) => {
        const now = Date.now();
        this.setState({
            timers: this.state.timers.map((timer) => {
                if(timer.id === timerId) {
                    const lastElapsed = now - timer.runningSince;
                    return Object.assign({}, timer,{
                        elapsed: timer.elapsed + lastElapsed,
                        runningSince: null
                    })
                } else {
                    return timer;
                }
            })
        })
    }
。。。
					<EditableTimerList
                        timers={this.state.timers}
                        onFormSubmit={this.handleEditFormSubmit}
                        onTrashClick={this.handleTrashClick}
                        onStartClick={this.handleStartClick} //add
                        onStopClick={this.handleStopClick}  //add
                    />
上面的计时器状态不可以保存,需要保存在服务器上
见下一章