文章目录
- 1. 将应用程序分解为组件
- 2. 构建应用静态版本
- 3. 哪些组件是有状态的
- 4. 每个 state 应该在哪个组件里
- 5. 硬编码初始化state
- 6. 添加反向数据流
- 7. 添加服务器通信
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
本章学习的目标是做一个定时器
1. 将应用程序分解为组件
2. 构建应用静态版本
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'><EditableTimertitle='学习react'project='react web'elasped='3600'runningSince={null}editFormOpen={false}/><EditableTimertitle='学习python'project='deep learning'elasped='3600'runningSince={null}editFormOpen={true}/></div>)}
}class EditableTimer extends React.Component {render() {if (this.props.editFormOpen) {return (<TimerFormtitle={this.props.title}project={this.props.project}/>)} else {return (<Timertitle={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 的分支
3. 哪些组件是有状态的
- 是通过 props 从 父组件 传递进来的吗?是的话,它很可能不是 state
- 不随时间变化,很可能不是 state
- 可以根据其他 state 或 props 计算得到,不是 state
原则就是用尽可能少的 state
4. 每个 state 应该在哪个组件里
如何确定,步骤:
- 标识基于该state渲染的组件
- 查找state共同所有者
- 较高层次的组件拥有该state
- 找不到的话,创建新组件来保存 state,并置于层次结构的上方
5. 硬编码初始化state
props 可理解为 : 不可改变的 state
- TimersDashBoard
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'><EditableTimerListtimers={this.state.timers}/><ToggleableTimerForm isOpen={false}/></div></div>)}
}class EditableTimerList extends React.Component {render() {const timers = this.props.timers.map((timer) => (<EditableTimerkey={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 (<TimerFormid={this.props.id}title={this.props.title}project={this.props.project}/>)} else {return (<Timerid={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'><buttonclassName='ui basic button icon'onClick={this.handleFormOpen}><i className='plus icon'/></button></div>)}}
}
6. 添加反向数据流
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>)}
}
- ToggleableTimerForm 组件
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 (<TimerFormonFormSubmit={this.handleFormSubmit}onFormClose={this.handleFormClose}/>)} else {return (<div className='ui basic content center aligned segment'><buttonclassName='ui basic button icon'onClick={this.handleFormOpen}><i className='plus icon'/></button></div>)}}
}
- TimersDashBoard 组件
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'><EditableTimerListtimers={this.state.timers}/><ToggleableTimerFormonFormSubmit={this.handleCreateFormSubmit}/></div></div>)}
还不能开始,删除,修改
- 更新计时器
编辑 Timer 组件,添加函数 onEditClick
<spanclassName='right floated edit icon'onclick={this.props.onEditClick}><i className='edit icon'/></span>
- EditableTimer 组件
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 (<TimerFormid={this.props.id}title={this.props.title}project={this.props.project}onFormSubmit={this.handleFormSubmit}onFormClose={this.handleFormClose}/>)} else {return (<Timerid={this.props.id}title={this.props.title}project={this.props.project}elapsed={this.props.elapsed}runningSince={this.props.runningSince}onEditClick={this.handleEditClick}/>)}}
- EditableTimerList 添加
onFormSubmit={this.props.onFormSubmit}
class EditableTimerList extends React.Component {render() {const timers = this.props.timers.map((timer) => (<EditableTimerkey={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>)}
}
- 顶层组件 TimersDashBoard
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
<EditableTimerListtimers={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 (<Timerid={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
<EditableTimerkey={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)})}。。。<EditableTimerListtimers={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 最底部添加 新组件
<TimerActionButtontimerIsRunning={!!this.props.runningSince}onStartClick={this.handleStartClick}onStopClick={this.handleStopClick}/>
TimerActionButton
class TimerActionButton extends React.Component {render() {if (this.props.timerIsRunning) {return (<divclassName='ui bottom attached red basic button'onClick={this.props.onStopClick}>Stop</div>)} else {return (<divclassName='ui bottom attached green basic button'onClick={this.props.onStartClick}>Start</div>)}}
}
这些事件需要在上层结构中传递
EditableTimer
<Timerid={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} // addonStopClick={this.props.onStopClick} // add/>
EditableTimerList
<EditableTimerkey={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} // addonStopClick={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;}})})}。。。<EditableTimerListtimers={this.state.timers}onFormSubmit={this.handleEditFormSubmit}onTrashClick={this.handleTrashClick}onStartClick={this.handleStartClick} //addonStopClick={this.handleStopClick} //add/>
7. 添加服务器通信
上面的计时器状态不可以保存,需要保存在服务器上
见下一章