我们掌握了react的基本语法,我们尝试实现一个todos的案例.

素材

image.png

功能实现

修改解构

  • 需要动态渲染的内容复制到JSX中
  • 修改JSX中的注释为 {/* */} 形式
  • 修改原生HTML属性为驼峰类型

    开发顺序

  1. 实现新增
  2. 实现列表展示
  3. 实现是否完成状态切换
  4. 实现item left
  5. 全选/取消全选
    1. 注意setState异步问题
  6. 双击编辑
    1. 注意取消还原的问题
  7. 删除
  8. 状态过滤
  9. 清除完成

b.gif
代码部分:

  1. class Todos extends React.Component {
  2. constructor(props){
  3. super(props);
  4. this.state = {
  5. newTodoValue : '',
  6. todoList: [],
  7. itemLeft: 0 , // 未完成剩余数量
  8. allCheckState: false, // 全选状态
  9. preEditValue: '',// 备份编辑前的内容
  10. fitlerState: 'all', //all, active, completed
  11. }
  12. this.handleNewTodo = this.handleNewTodo.bind(this);
  13. this.handleNewTodoEnter = this.handleNewTodoEnter.bind(this);
  14. this.regetItemLeft = this.regetItemLeft.bind(this);
  15. this.handleCheckAll = this.handleCheckAll.bind(this);
  16. this.handleClearCompleted = this.handleClearCompleted.bind(this);
  17. }
  18. // 处理新增表单value
  19. handleNewTodo(e){
  20. this.setState({
  21. newTodoValue: e.target.value
  22. })
  23. }
  24. // 处理新增事件
  25. handleNewTodoEnter(e){
  26. // 内容为空 不处理
  27. if(!e.target.value) return;
  28. if(e.keyCode === 13){
  29. // 复制todoList临时变量
  30. var todoList = [...this.state.todoList];
  31. // 构建todo对象
  32. var todo = {
  33. id: new Date().getTime(),
  34. isEdit: false,
  35. text: e.target.value,
  36. isDone: false,
  37. }
  38. todoList.push(todo);
  39. //更新到todoLis
  40. this.setState({
  41. todoList
  42. });
  43. // 重新计算剩余数量
  44. this.regetItemLeft();
  45. // 清除新增输入框内容
  46. this.setState({
  47. newTodoValue: ''
  48. })
  49. }
  50. // esc 取消
  51. if(e.keyCode === 27){
  52. this.setState({
  53. newTodoValue: ''
  54. })
  55. }
  56. }
  57. // 处理全选
  58. handleCheckAll(){
  59. if(this.state.todoList.length == 0) return;
  60. // 注意: setState是异步的,必须在第二个回调才能获取最新状态
  61. // # https://react.docschina.org/docs/state-and-lifecycle.html
  62. this.setState(state=>({
  63. allCheckState: !state.allCheckState
  64. }),()=>{
  65. // 更改每一项
  66. var todoList = [...this.state.todoList];
  67. todoList.map(item=>{
  68. item.isDone = this.state.allCheckState;
  69. return item;
  70. })
  71. console.log('todoList',todoList)
  72. //
  73. this.setState(state=>({
  74. todoList
  75. }))
  76. // // 更新剩余数量
  77. this.regetItemLeft();
  78. })
  79. }
  80. // 某一行的checkbox被修改
  81. handleCheckOne(id,e){
  82. var todoList = [...this.state.todoList];
  83. // 找到目标元素下标
  84. var index = todoList.findIndex(item=>item.id == id);
  85. todoList[index].isDone = !todoList[index].isDone;
  86. // 更新
  87. this.setState({
  88. todoList
  89. })
  90. // 计算剩余未完成数量
  91. this.regetItemLeft();
  92. }
  93. // 编辑行数据变化
  94. handleEditChange(id,e){
  95. var todoList = [...this.state.todoList];
  96. var index = todoList.findIndex(item=>item.id == id);
  97. todoList[index].text = e.target.value;
  98. this.setState({
  99. todoList
  100. })
  101. }
  102. //双击编辑
  103. handleEdit(id,e){
  104. var todoList = [...this.state.todoList];
  105. // 排他 只能有一个处于编辑状态
  106. var beforeEditIndex = todoList.findIndex(item=>item.isEdit);
  107. if(beforeEditIndex>-1){
  108. todoList[beforeEditIndex].isEdit = false;
  109. }
  110. // 找到目标元素下标
  111. var index = todoList.findIndex(item=>item.id == id);
  112. todoList[index].isEdit = true;
  113. this.setState({
  114. todoList
  115. },()=>{
  116. // 通过dom关系找到目标元素
  117. e.target.parentNode.nextElementSibling.focus();
  118. })
  119. // 备份旧内容 用于esc取消还原
  120. this.setState({
  121. preEditValue: todoList[index].text
  122. })
  123. }
  124. // 编辑行确定
  125. handleOkOrCancel(id,e){
  126. var todoList = [...this.state.todoList];
  127. // 找到目标元素下标
  128. var index = todoList.findIndex(item=>item.id == id);
  129. // 确定是esc还是enter
  130. if(e.keyCode === 13){
  131. todoList[index].isEdit = false;
  132. this.setState({
  133. todoList,
  134. preEditValue: ''
  135. })
  136. }else if(e.keyCode === 27){
  137. // 还原
  138. todoList[index].text = this.state.preEditValue;
  139. todoList[index].isEdit = false;
  140. this.setState({
  141. todoList,
  142. preEditValue: ''
  143. })
  144. }
  145. }
  146. // 计算剩余
  147. regetItemLeft(){
  148. // 如果没有数据
  149. if(this.state.todoList.length == 0){
  150. // 处理全选
  151. this.setState({
  152. allCheckState: false
  153. })
  154. return;
  155. }
  156. // 设置剩余长度
  157. var itemLefts = this.state.todoList.filter(item=>!item.isDone);
  158. this.setState({
  159. itemLeft: itemLefts.length
  160. })
  161. // 响应全选
  162. this.setState({
  163. allCheckState: itemLefts.length==0
  164. })
  165. }
  166. // 删除
  167. removeItem(id){
  168. var todoList = [...this.state.todoList];
  169. // 找到目标元素下标
  170. var index = todoList.findIndex(item=>item.id == id);
  171. todoList.splice(index,1);
  172. this.setState({
  173. todoList
  174. })
  175. }
  176. // 改变filterState
  177. changeFilterState(state){
  178. this.setState({
  179. fitlerState: state
  180. })
  181. }
  182. // 清除所有完成的
  183. handleClearCompleted(){
  184. var todoList = [...this.state.todoList];
  185. todoList = todoList.filter(item=>!item.isDone);
  186. this.setState({
  187. todoList
  188. })
  189. }
  190. render() {
  191. return (
  192. <section className="todoapp">
  193. <header className="header">
  194. <h1>todos</h1>
  195. <input
  196. className="new-todo"
  197. placeholder="What needs to be done?"
  198. autoFocus
  199. value = {this.state.newTodoValue}
  200. onChange = {this.handleNewTodo}
  201. onKeyUp= {this.handleNewTodoEnter}
  202. />
  203. </header>
  204. {/* This section should be hidden by default and shown when there are todos */}
  205. <section className="main">
  206. <input id="toggle-all" className="toggle-all" type="checkbox" checked={this.state.allCheckState} onChange={this.handleCheckAll}/>
  207. <label htmlFor="toggle-all">Mark all as complete</label>
  208. <ul className="todo-list">
  209. {/* These are here just to show the structure of the list items */}
  210. {/* List items should get the class `editing` when editing and `completed` when marked as completed */}
  211. {
  212. this.state.todoList.map(item=>{
  213. // 待办
  214. if(this.state.fitlerState == 'active'){
  215. if(item.isDone) return null;
  216. }
  217. // 已完成
  218. if(this.state.fitlerState == 'completed'){
  219. if(!item.isDone) return null;
  220. }
  221. return(
  222. <li className={item.isDone?'completed':''} onDoubleClick={e=>this.handleEdit(item.id,e)} key={item.id}>
  223. <div className="view" style={{display: item.isEdit?'none':'block'}}>
  224. <input className="toggle" type="checkbox" checked={item.isDone} onChange={e=>this.handleCheckOne(item.id,e)}/>
  225. <label>{item.text}</label>
  226. <button className="destroy" onClick={e=>this.removeItem(item.id)}></button>
  227. </div>
  228. <input className="edit" onKeyUp={e=>this.handleOkOrCancel(item.id,e)} style={{display: !item.isEdit?'none':'block'}} value={item.text} onChange={e=>this.handleEditChange(item.id,e)} />
  229. </li>
  230. )
  231. })
  232. }
  233. </ul>
  234. </section>
  235. {/* This footer should be hidden by default and shown when there are todos */}
  236. <footer className="footer">
  237. {/* This should be `0 items left` by default */}
  238. <span className="todo-count">
  239. <strong>{this.state.itemLeft}</strong> item left
  240. </span>
  241. {/* Remove this if you don't implement routing */}
  242. <ul className="filters">
  243. <li onClick={e=>this.changeFilterState('all')}>
  244. <a className={this.state.fitlerState=='all'?'selected':''} href="#/">
  245. All
  246. </a>
  247. </li>
  248. <li onClick={e=>this.changeFilterState('active')}>
  249. <a className={this.state.fitlerState=='active'?'selected':''} href="#/active">Active</a>
  250. </li>
  251. <li onClick={e=>this.changeFilterState('completed')}>
  252. <a className={this.state.fitlerState=='completed'?'selected':''} href="#/completed">Completed</a>
  253. </li>
  254. </ul>
  255. {/* Hidden if no completed items are left ↓ */}
  256. <button className="clear-completed" onClick={this.handleClearCompleted}>Clear completed</button>
  257. </footer>
  258. </section>
  259. );
  260. }
  261. }
  262. ReactDOM.render(<Todos/>,document.getElementById('app'));

封装组件

拆分组件

把功能拆分为 <Todos /> <TodoItem /> <FooterBar /> 三个组件.
其实todos 这个例子并不适合我们这样组件化,因为组件和组件之间存在大量的数据联动,我们在处理某个组件的变量的同时需要同时考虑其他组件的变量,这个不仅增加了代码量,而且提高了代码逻辑的复杂度.那这里为什么要封装组件呢?
但通过这个案例,可充分体现react的设计思路:

  • 提高代码复用性
  • 体现react组件开发的优势
  • 体会并理解react 单向数据流 , 自上而下 的state 思想
    • 关于state:

状态提升:
通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。让我们看看它是如何运作的。
任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。

  1. // 单个todo组件
  2. class TodoItem extends React.Component {
  3. constructor(props){
  4. super(props);
  5. }
  6. // 处理一个变化
  7. handleCheckOne(id){
  8. this.props.handleCheckOne(id)
  9. }
  10. // 双击编辑
  11. handleEdit(id,e){
  12. // 通过dom关系找到目标元素
  13. var target = e.target.parentNode.nextElementSibling;
  14. this.props.handleEdit(id,target);
  15. }
  16. handleEditChange(id,e){
  17. var value = e.target.value;
  18. this.props.handleEditInputChange(id,value);
  19. }
  20. handleOkOrCancel(id,e){
  21. this.props.handleEditOkOrCancel(id,e);
  22. }
  23. render(){
  24. return(
  25. <li className={this.props.isDone?'completed':''}>
  26. <div className="view" style={{display: this.props.isEdit?'none':'block'}}>
  27. <input className="toggle" type="checkbox" checked={this.props.isDone} onChange={e=>this.handleCheckOne(this.props.id,e)}/>
  28. <label onDoubleClick={e=>this.handleEdit(this.props.id,e)}>{this.props.text}</label>
  29. <button className="destroy" onClick={e=>this.props.removeItem(this.props.id)}></button>
  30. </div>
  31. <input className="edit" onKeyUp={e=>this.handleOkOrCancel(this.props.id,e)} style={{display: !this.props.isEdit?'none':'block'}} value={this.props.text} onChange={e=>this.handleEditChange(this.props.id,e)} />
  32. </li>
  33. )
  34. }
  35. }
  36. class FooterBar extends React.Component {
  37. constructor(props){
  38. super(props);
  39. this.changeFilterState=this.changeFilterState.bind(this);
  40. this.handleClearCompleted=this.handleClearCompleted.bind(this);
  41. }
  42. changeFilterState(state){
  43. this.props.changeFilterState(state);
  44. }
  45. handleClearCompleted(){
  46. this.props.handleClearCompleted();
  47. }
  48. render(){
  49. return(
  50. <footer className="footer">
  51. {/* This should be `0 items left` by default */}
  52. <span className="todo-count">
  53. <strong>{this.props.itemLeft||0}</strong> item left
  54. </span>
  55. {/* Remove this if you don't implement routing */}
  56. <ul className="filters">
  57. <li onClick={e=>this.changeFilterState('all')}>
  58. <a className={this.props.fitlerState=='all'?'selected':''} href="#/">
  59. All
  60. </a>
  61. </li>
  62. <li onClick={e=>this.changeFilterState('active')}>
  63. <a className={this.props.fitlerState=='active'?'selected':''} href="#/active">Active</a>
  64. </li>
  65. <li onClick={e=>this.changeFilterState('completed')}>
  66. <a className={this.props.fitlerState=='completed'?'selected':''} href="#/completed">Completed</a>
  67. </li>
  68. </ul>
  69. {/* Hidden if no completed items are left ↓ */}
  70. <button className="clear-completed" onClick={this.handleClearCompleted}>Clear completed</button>
  71. </footer>
  72. )
  73. }
  74. }
  75. class Todos extends React.Component {
  76. constructor(props){
  77. super(props);
  78. this.state = {
  79. newTodoValue : '',
  80. todoList: [],
  81. itemLeft: 0 , // 未完成剩余数量
  82. allCheckState: false, // 全选状态
  83. preEditValue: '',// 备份编辑前的内容
  84. fitlerState: 'all', //all, active, completed
  85. }
  86. this.handleNewTodo = this.handleNewTodo.bind(this);
  87. this.handleNewTodoEnter = this.handleNewTodoEnter.bind(this);
  88. this.handleCheckOne = this.handleCheckOne.bind(this);
  89. this.handleCheckAll = this.handleCheckAll.bind(this);
  90. this.handleEdit = this.handleEdit.bind(this);
  91. this.handleEditChange = this.handleEditChange.bind(this);
  92. this.handleOkOrCancel = this.handleOkOrCancel.bind(this);
  93. this.removeItem = this.removeItem.bind(this);
  94. this.changeFilterState = this.changeFilterState.bind(this);
  95. this.handleClearCompleted = this.handleClearCompleted.bind(this);
  96. }
  97. // 响应新增
  98. handleNewTodo(e){
  99. this.setState({
  100. newTodoValue: e.target.value
  101. })
  102. }
  103. // 处理新增
  104. handleNewTodoEnter(e){
  105. // enter or esc
  106. if(e.keyCode === 13){
  107. var todo = {
  108. id: new Date().getTime(),
  109. text: e.target.value,
  110. isEdit: false,
  111. isDone: false
  112. }
  113. var todoList = [...this.state.todoList];
  114. todoList.push(todo);
  115. // 添加新增 并修改表单内容
  116. // 因为setState是异步的
  117. this.setState({
  118. todoList,
  119. newTodoValue: ''
  120. },()=>{
  121. this.regetItemLeft();
  122. })
  123. }else if(e.keyCode === 27){
  124. this.setState({
  125. newTodoValue: ''
  126. })
  127. }
  128. }
  129. // 当修改是否选中状态
  130. handleCheckOne(id){
  131. console.log('id',id)
  132. var todoList = [...this.state.todoList];
  133. var todo = todoList.find(item=>item.id == id);
  134. todo.isDone = !todo.isDone;
  135. // 修改state
  136. this.setState({
  137. ...todoList
  138. })
  139. this.regetItemLeft();
  140. }
  141. // 全选
  142. handleCheckAll(){
  143. if(this.state.todoList.length == 0) return;
  144. // 注意: setState是异步的,必须在第二个回调才能获取最新状态
  145. // # https://react.docschina.org/docs/state-and-lifecycle.html
  146. this.setState(state=>({
  147. allCheckState: !state.allCheckState
  148. }),()=>{
  149. // 更改每一项
  150. var todoList = [...this.state.todoList];
  151. todoList.map(item=>{
  152. item.isDone = this.state.allCheckState;
  153. return item;
  154. })
  155. //
  156. this.setState(state=>({
  157. todoList
  158. }),()=>{
  159. // 更新剩余数量
  160. this.regetItemLeft();
  161. })
  162. })
  163. }
  164. // 计算剩余
  165. regetItemLeft(){
  166. // 如果没有数据
  167. if(this.state.todoList.length == 0){
  168. // 处理全选
  169. this.setState({
  170. allCheckState: false
  171. })
  172. return;
  173. }
  174. // 设置剩余长度
  175. var itemLefts = this.state.todoList.filter(item=>!item.isDone);
  176. this.setState({
  177. itemLeft: itemLefts.length
  178. })
  179. console.log(itemLefts.length)
  180. // 响应全选
  181. this.setState({
  182. allCheckState: itemLefts.length==0
  183. })
  184. }
  185. //双击编辑
  186. handleEdit(id,target){
  187. var todoList = [...this.state.todoList];
  188. // 排他 只能有一个处于编辑状态
  189. var beforeEditIndex = todoList.findIndex(item=>item.isEdit);
  190. if(beforeEditIndex>-1){
  191. todoList[beforeEditIndex].isEdit = false;
  192. }
  193. // 找到目标元素下标
  194. var index = todoList.findIndex(item=>item.id == id);
  195. todoList[index].isEdit = true;
  196. this.setState({
  197. todoList
  198. },()=>{
  199. target.focus();
  200. })
  201. // 备份旧内容 用于esc取消还原
  202. this.setState({
  203. preEditValue: todoList[index].text
  204. })
  205. }
  206. // 编辑行数据变化
  207. handleEditChange(id,value){
  208. var todoList = [...this.state.todoList];
  209. var index = todoList.findIndex(item=>item.id == id);
  210. todoList[index].text = value;
  211. this.setState({
  212. todoList
  213. })
  214. }
  215. // 编辑行确定
  216. handleOkOrCancel(id,e){
  217. var todoList = [...this.state.todoList];
  218. // 找到目标元素下标
  219. var index = todoList.findIndex(item=>item.id == id);
  220. // 确定是esc还是enter
  221. if(e.keyCode === 13){
  222. todoList[index].isEdit = false;
  223. this.setState({
  224. todoList,
  225. preEditValue: ''
  226. })
  227. }else if(e.keyCode === 27){
  228. // 还原
  229. todoList[index].text = this.state.preEditValue;
  230. todoList[index].isEdit = false;
  231. this.setState({
  232. todoList,
  233. preEditValue: ''
  234. })
  235. }
  236. }
  237. removeItem(id){
  238. var todoList = [...this.state.todoList];
  239. // 找到目标元素下标
  240. var index = todoList.findIndex(item=>item.id == id);
  241. todoList.splice(index,1);
  242. this.setState({
  243. todoList
  244. })
  245. }
  246. // 改变filterState
  247. changeFilterState(state){
  248. this.setState({
  249. fitlerState: state
  250. })
  251. }
  252. // 清除所有完成的
  253. handleClearCompleted(){
  254. var todoList = [...this.state.todoList];
  255. todoList = todoList.filter(item=>!item.isDone);
  256. this.setState({
  257. todoList
  258. })
  259. }
  260. render() {
  261. return (
  262. <section className="todoapp">
  263. <header className="header">
  264. <h1>todos</h1>
  265. <input
  266. className="new-todo"
  267. placeholder="What needs to be done?"
  268. autoFocus
  269. value = {this.state.newTodoValue}
  270. onChange = {this.handleNewTodo}
  271. onKeyUp= {this.handleNewTodoEnter}
  272. />
  273. </header>
  274. {/* This section should be hidden by default and shown when there are todos */}
  275. <section className="main">
  276. <input id="toggle-all" className="toggle-all" type="checkbox" checked={this.state.allCheckState} onChange={this.handleCheckAll}/>
  277. <label htmlFor="toggle-all">Mark all as complete</label>
  278. <ul className="todo-list">
  279. {
  280. this.state.todoList.map(item=>{
  281. // 待办
  282. if(this.state.fitlerState == 'active'){
  283. if(item.isDone) return null;
  284. }
  285. // 已完成
  286. if(this.state.fitlerState == 'completed'){
  287. if(!item.isDone) return null;
  288. }
  289. return(
  290. /*把整个item对象传入组件*/
  291. <TodoItem key={item.id} {...item}
  292. handleEdit={this.handleEdit}
  293. handleCheckOne={this.handleCheckOne}
  294. handleEditInputChange={this.handleEditChange}
  295. handleEditOkOrCancel={this.handleOkOrCancel}
  296. removeItem={this.removeItem}/>
  297. )
  298. })
  299. }
  300. </ul>
  301. </section>
  302. <FooterBar fitlerState={this.state.fitlerState} itemLeft={this.state.itemLeft} changeFilterState={this.changeFilterState} handleClearCompleted={this.handleClearCompleted}/>
  303. </section>
  304. );
  305. }
  306. }
  307. ReactDOM.render(<Todos/>,document.getElementById('app'));

什么时候封装组件?

至于什么时候需要组件封装,这个需要我们在以后的开发过程中根据实际情况来体会.这里我个人总结的两个原则:

  • 考虑功能模块的复用性
  • 提高代码的可读性,组件的逻辑性

    总结

    我们到目前为止,react的基本语法已经学完了.如果大家有vue基础,相信能很快理解并掌握react. 当然react还有路由管理,状态管理等相关知识点,我们会在接下来的项目开发中,带大家直接上手,在开发中理解并学会其应用.