服务菜单
最好的学习就是在实战中成长,做一个《小姐姐服务菜单》应用,练习前面的知识和学习新知识
新建小姐姐组件
先在SRC的目录下面,新建一个文件Xiaojiejie.js
然后写一个基本的HTML结构
import React,{Component} from 'react';
class Xiaojiejie extends Component{
render(){
return (
<div>
<div><input /><button>增加服务</button></div>
<ul>
<li>头部按摩</li>
<li>精油推背</li>
</ul>
</div>
)
}
}
export default Xiaojiejie
这个文件现在还没有功能,只是写完了一个小组件
我们把入口文件的<App/>
组件换成Xiaojiejie
组件
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'
import Xiaojiejie from './Xiaojiejie'
ReactDOM.render(<Xiaojiejie />,document.getElementById('root'))
组件外层包裹原则
上面的代码,如果去掉最外层的<div>
,就会报错
Failed to compile
./src/Xiaojiejie.js
SyntaxError: D:\ReactDemo\demo01\src\Xiaojiejie.js: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>? (8:16)
React要求必须在一个组件的最外层进行包裹
Fragment标签
加上最外层的div
,组件就完全正常
但有的时候,布局就偏不需要这个最外层的标签
- 例如Flex布局时,外层不能有包裹元素
我们可以使用<Fragment>
标签
import React,{Component,Fragment} from 'react';
class Xiaojiejie extends Component{
render(){
return (
<Fragment>
<div><input /><button>增加服务</button></div>
<ul>
<li>头部按摩</li>
<li>精油推背</li>
</ul>
</Fragment>
)
}
}
export default Xiaojiejie
这时在浏览器的Elements
中查看,发现已经没有外层的包裹了
响应式设计原理、数据的绑定方法
响应式设计和数据的绑定
React不建议直接操作DOM元素,而是通过数据进行驱动,改变界面中的效果
React会根据数据的变化,自动帮助你完成界面的改变
因此在写React代码时,你无需关注DOM相关的操作,只需关注数据的操作就可以了
-
需求是增加小姐姐的服务项,就需要定义数据
-
数据定义在Xiaojiejie组件中的constructor构造函数里
// js的构造函数,由其他任何函数执行
constructor(props) {
super(props) // 调用父类的构造函数,固定写法
this.state = {
inputValue: '', // input中的值
list: [] // 服务列表
}
}
React中数据绑定和Vue差不多,采用字面量
的形式(自己起的名字),使用{}
来标注
把inputvalue
值绑定到input
框中,说白了就是在JSX中使用js代码
<input value={this.state.inputValue}>
绑定事件
这时在界面的文本框中输入值,是没有任何变化的
这是因为我们强制绑定了inputValue
的值
如果要想改变,需要绑定响应事件,改变inputValue的值
- 绑定一个改变事件,这个事件执行
inputChange()
<input value={this.state.inputValue} onChange={this.inputChange}>
根据函数执行顺序,在render()方法的下面建立inputChange()
方法
inputChange(e){
console.log(e);
}
inputChange(e){
console.log(e.target.value);
}
这时候控制台打印输出值,看到获得了输入的值,想当然的认为直接改变inputValue的值就可以,但是会报错
inputChange(e){
console.log(e.target.value);
this.state.inputValue = e.target.value
}
TypeError: Cannot read property 'state' of undefined
这里犯了两个错误
this
指向不对,需要重新用bind
设置一下指向(ES6语法)
<input value={this.state.inputValue} onChange={this.inputChange.bind(this)}>
- React中改变值需要用
this.setState
方法
inputChange(e){
this.setState({
inputValue:e.target.value
})
}
整体Code
// Xiaojiejie.js
import React, { Component, Fragment } from 'react';
class Xiaojiejie extends Component {
// js的构造函数,由其他任何函数执行
constructor(props) {
super(props) // 调用父类的构造函数,固定写法
this.state = {
inputValue: '', // input中的值
list: [] // 服务列表
}
}
render() {
return (
<Fragment>
<div>
<input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
<button>增加服务</button>
</div>
<ul>
<li>头部按摩</li>
<li>精油推背</li>
</ul>
</Fragment>
)
}
inputChange(e){
// console.log(e.target.value);
// this.state.inputValue = e.target.value
this.setState({
inputValue:e.target.value
})
}
}
export default Xiaojiejie
添加服务、列表渲染
让列表数据化
现在的列表是写死的两个<li>
标签
要变成动态显示的,就需要把这个列表进行数据化,再用js代码,循环在页面上
先给list数组增加两个数组元素list:['基础按摩','精油推背']
有了数据,可以在JSX部分进行循环输出
render() {
return (
<Fragment>
<div>
<input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
<button>增加服务</button>
</div>
<ul>
{
this.state.list.map((item,index)=>{
return <li>{item}</li>
})
}
</ul>
</Fragment>
)
}
这样数据就不再是固定的了,而是动态管理的
增加服务选项
要增加服务选项,需要在按钮上绑定一个方法this.addList(这个方法还没有,接下来建立)
<button onClick={this.addList.bind(this)}>增加服务</button>
// 增加服务的按钮响应方法
addList(){
this.setState({
list:[...this.state.list,this.state.inputValue]
})
}
...
是ES6的新语法,叫做扩展运算符
把list数组进行了分解,形成了新的数组,然后再进行组合,这种写法更简单和直观
list:[...this.state.list,this.state.inputValue]
等价于
list:['基础按摩','精油推背',this.state.inputValue]
解决Key值错误
Warning: Each child in a list should have a unique "key" prop.
在用map循环时,需要设置一个不同的值,这是React的要求
可以暂时用index+item的形式来实现
<ul>
{
this.state.list.map((item,index)=>{
// index 循环的索引,0,1,2,3,有几个元素它索引几遍
// 但不能只把index作为key,因为项目中,你的索引可能有好多项,一个页面中有好多循环,这时用索引它就会重复,会报错
// 索引+选项,重复的概率就会很小
return <li key={index+item} >{item}</li>
})
}
</ul>
一般来说,input输入完后会清空
// 增加服务的按钮响应方法
addList(){
this.setState({
list:[...this.state.list,this.state.inputValue],
inputValue:''
})
}
删除服务选项
数组下标的传递
要删除一个东西,就要得到数组里的一个编号,也就是下标
传递下标要有事件产生,先来绑定一个双击事件
<ul>
{
this.state.list.map((item,index)=>{
return (
<li
key={index+item}
onClick={this.deleteItem.bind(this,index)}
>
{item}
</li>
)
})
}
</ul>
为了看的更清晰,在return
部分加了()
这样就可以换行编写JSX
代码了,在onClick
绑定了deleteItem
方法
deleteItem
// 删除单项服务
deleteItem(index){
console.log(index)
let list = this.state.list
// 从索引项开始删除,删除一位
list.splice(index,1)
this.setState({
list:list
})
}
获得了数据下标后,删除数据就变得容易起来
先声明一个局部变量,然后利用传递过来的下标删除数组中的值,最后用setState
更新数据就行
这里有个坑需要踩,有的伙伴认为这样写也是正确的
deleteItem(index){
this.state.list.splice(index,1)
this.setState({
list:this.state.list
})
}
请记住React是禁止直接操作state的,虽然这样也管用,但是在后期的性能优化上会有很多麻烦,一定不要这样操作