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}
/>
TimerActionButton
class 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
/>
上面的计时器状态不可以保存,需要保存在服务器上
见下一章