前端笔记之React(二)组件内部State&React实战&表单元素的受控
一、组件内部的State
1.1 state
state叫状态,是每一个类式组件都有的属性,但函数式组件,没有state。
state是一个对象,什么值都可以定义。
在任何类式组件的构造函数中,可以用this.state = {} 来给类的实例添加state属性,表示“状态”。
在render()函数的return中,可以用{this.state.a}插值来显示出每一个属性的值
import React from "react";
export default class App extends React.Component {
constructor() {
super();
//组件的内部状态,state属性
this.state = {
a : 100
}
}
render() {
return <div>
<h1>{this.state.a}</h1>
</div>
}
}
1.2 setState()
点击按钮之后,让100加1
import React from "react";
import ReactDOM from "react-dom";
export default class App extends React.Component {
constructor() {
super();
this.state = {
a : 100
}
}
render() {
return <div>
<button onClick={()=>{
this.setState({
a : this.state.a + 1
})
}}>按我</button>
<h1>{this.state.a}</h1>
</div>
}
}
注意:
中事件监听,要写在标签上。
事件名onClick而不是onclick,注意大写字母。因为React将事件名都进行了拓展,所以onClick是React自己的方法。同理,所有事件名on后面的首字母都是大写:onMouseEnter、onDoubleClick、onKeyDown。
后面紧跟{},表示插值。大括号中是一个箭头函数,这个函数必须是箭头函数,否则this错误。
<button onClick={()=>{ }}></button>
setState是定义在React.Component类中的方法,所以任何一个组件都能够无脑调用this.setState()。表示“设置state”。
this.setState({要设置的k : 新的v});
setState不仅能够改变实例的state的属性值,而且能产生视图刷新。而如果不用setState(),只是让state的属性值进行改变,视图是不刷新的。
错误的:
export default class App extends React.Component {
constructor() {
super();
this.state = {
a : 100
}
}
render() {
return <div>
<button onClick={()=>{
this.state.a++;
}}>按我</button>
<h1>{this.state.a}</h1>
</div>
}
}
1.3提炼出事件处理函数
之前的onClick后面直接跟上了{()=>{}},实际上可以提出来,封装成函数。
import React from "react";
export default class App extends React.Component{
constructor(){
super();
this.state = {
a : 100
}
}
add(){
this.setState({
a:this.state.a + 1
});
}
render(){
return <div>
<h1>{this.state.a}</h1>
<button onClick={()=>{this.add()}}>按我加1</button>
<button onClick={this.add.bind(this)}>按我加1</button>
</div>
}
};
方法的执行可以不通过箭头函数,但是不好,因为这样写不能传递参数
注意,提炼成为组件的方法(实际上写在了构造器的prototype上,实例的原型上),在onClick调用的时候,必须写bind(this),将调用的这个函数的上下文绑定为组件的实例。可以当做是一个固定的语法!
如何传参数?
import React from "react";
import ReactDOM from "react-dom";
export default class App extends React.Component {
constructor() {
super();
this.state = {
a : 100
}
}
//单击事件的处理函数
add(n){
this.setState({
a : this.state.a + n
});
}
render() {
return <div>
<button onClick={()=>{this.add(1)}}>按我</button>
<button onClick={()=>{this.add(2)}}>按我</button>
<button onClick={()=>{this.add(3)}}>按我</button>
<h1>{this.state.a}</h1>
<div style={{
"width" : this.state.a + "px",
"height": this.state.a + "px",
"backgroundColor" : "orange",
"transform" : "rotate(" + this.state.a + "deg)"
}}></div>
</div>
}
}
1.4 MVVM模式
React、Vue以及已经过时Angular都是MVVM模式,都有一个特点,就是:
数据驱动视图:数据变化了,视图自动变化
MVVM模式经典的4句话:
1)数据变化,视图就会自动变化
2)视图变化的原因,一定是数据变化了
3)数据是视图的本质
4)视图是数据的表现
我们以后再也不用关心DOM结构了,只关心数据,数据变化,视图自动变化
现在只需要用setState()来改变组件的实例的state,视图就会自动变化,后面你将知道,视图变化的原因是因为组件进入了新的生命周期,也将知道视图更新的效率因为有的Virtual DOM从而变的很快。
二、案例
2.1组件state的增删改查
知识点:html标签可以加ref(reference引用)属性,在组件内部可以通过this.refs来引用这个DOM元素。
<input type="text" ref="nameTxt"/>
获取标签的值:
var val = this.refs.nameTxt.value
import React from "react";
export default class App extends React.Component {
constructor() {
super();
this.state = {
arr: [
{ "id": 1, "name": "小明", "age": 12, "sex": "男" },
{ "id": 2, "name": "小红", "age": 13, "sex": "女" },
{ "id": 3, "name": "小刚", "age": 14, "sex": "男" },
{ "id": 4, "name": "小白", "age": 15, "sex": "男" }
]
}
}
//添加学员
addList(){
//获取值
var name = this.refs.nameTxt.value;
var age = this.refs.ageTxt.value;
var sex = this.refs.sexTxt.value;
//改变state必须用setState()方法
this.setState({
arr:[
// 原来的项是不变
...this.state.arr,
// 只增加一项
{
//id : 6
id:this.state.arr.reduce((a,b)=>{
return a.id > b.id ? a : b
}).id + 1,
name,
age,
sex
}
]
})
}
//删除列表
delList(id){
this.setState({
arr:this.state.arr.filter(item=>item.id !=id)
});
}
render(){
return <div>
<p>姓名:<input type="text" ref="nameTxt" /></p>
<p>年龄:<input type="text" ref="ageTxt" /></p>
<p>性别:<input type="text" ref="sexTxt" /></p>
<button onClick={()=>{this.addList()}}>添加</button>
<ul>
{
this.state.arr.map(item=>{
return <li key={item.id}>
{item.id} -- {item.name}--年龄{item.age}岁,性别{item.sex}
<button onClick={()=>{this.delList(item.id)}}>删除</button>
</li>
})
}
</ul>
</div>
}
}
React中都是纯函数编程。
2.2调色板
import React from "react";
export default class App extends React.Component{
constructor(){
super();
this.state = {
r : 20,
g : 200,
b : 123
}
}
//变化颜色
setColor(k,v){
this.setState({ [k] : v })
}
render(){
return <div>
<div style={{
"width":"200px",
"height":"200px",
"background":`rgb(${this.state.r},${this.state.g},${this.state.b})`}}>
</div>
<p>
<input
type="range"
max={255}
value = {this.state.r}
onChange={(e)=>{this.setColor("r",e.target.value)}}
/>
<span>{this.state.r}</span>
</p>
<p>
<input
type="range"
max={255}
value={this.state.g}
onChange={(e)=>{this.setColor("g",e.target.value)}}
/>
<span>{this.state.g}</span>
</p>
<p>
<input
type="range"
max={255}
value={this.state.b}
onChange={(e)=>{this.setColor("b",e.target.value)}}
/>
<span>{this.state.b}</span>
</p>
</div>
}
};
如果一个表单元素,和state的一个值进行了“关联”:
1)state的值就是表单元素的值;
2)改变表单元素,就会改变state的值。
我们叫做这个表单元素和数据进行了“双向数据绑定”,也叫作表单元素“受控”。
在React中,实现双向数据绑定(实现表单元素受控)的套路:
加上value属性实现从state中“要”值;
加上onChange事件实现“设置”state的值。
如果一个组件内部,所有表单元素都有state的数据,进行了双向数据绑定,此时称为“受控组件”。
<p>
<input
type="range"
min={0}
max={255}
value={this.state.b}
onChange={(e)=>{this.setColor("b" , e.target.value)}}
/>
</p>
简单的说“一个表单元素受控”,等价于“这个表单元素有一个值和他双向绑定”。
所有的表单元素受控,我们就说组件受控。
2.3微博发布框
结构:输入框、发布按钮、内容清空按钮,一串文字“当前88/140字”
当内容超过140字,则发布按钮不能点,文字变红
实时显示字数,当框中有内容,按钮可以点击清空
如果让一个元素是否使用某一个类,React官方建议安装classnames依赖
npm install --save classnames
import React from "react";
import classnames from "classnames";
export default class App extends React.Component{
constructor(){
super();
this.state = {
txt: ""
}
}
render(){
const length = this.state.txt.length;
return <div>
<textarea
cols="30"
rows="10"
value={this.state.txt}
onChange={(e)=>{this.setState({txt:e.target.value})}}
>
</textarea>
<p>
<button disabled={length == 0 || length>140}>发布</button>
<button disabled={length==0} onClick={()=>{this.setState({txt:""})}}>
清空
</button>
<span className={classnames({"danger":length > 140})}>
已写{length}/140字
</span>
</p>
</div>
}
};
三、表单元素的受控
什么是受控?
一个表单元素的value值和state中某个属性息息相关:
这个表单元素的值,来自于state中的属性
更改表单元素的值,能够更改state中的值
也叫双向数据绑定,不过React中称为“受控组件(Controller Component)”
Vue才叫双向数据绑定。
在React中,所有表单元素的受控方式一样,都是value={},onChange={}
3.1单行和多行文本框
import React from "react";
var classNames = require('classnames');
export default class App extends React.Component {
//构造函数
constructor() {
super();
this.state = {
a : "我是默认的a值",
b : 50,
}
}
render() {
return (
<div>
<p>
<input type="text"
value={this.state.a}
onChange={(e) => { this.setState({ a: e.target.value }) }}
/>
<span>{this.state.a}</span>
</p>
<p>
<input type="range"
value={this.state.b}
onChange={(e) => { this.setState({ b: e.target.value })}}
/>
<span>{this.state.b}</span>
</p>
</div>
)
}
};
3.2下拉菜单
this.state = {
c : "广州"
}
<p>
<select
value={this.state.c}
onChange={(e) => { this.setState({ c: e.target.value }) }}
>
<option value="广州">广州</option>
<option value="深圳">深圳</option>
<option value="佛山">佛山</option>
<option value="东莞">东莞</option>
<option value="云浮">云浮</option>
</select>
<span>{this.state.c}</span>
</p>
3.3单选按钮
单选按钮受控的套路:checked={}、value="" 、onChange={}
<p>
<input
type="radio"
name="sex"
value="男"
checked={this.state.e == '男'}
onChange={(e) => { this.setState({ d: e.target.value }) }}
/>男
<input
type="radio"
name="sex"
value="女"
checked={this.state.e == '女'}
onChange={(e) => { this.setState({ d: e.target.value })}}
/>女
<span>【{this.state.d}】</span>
</p>
3.4复选框受控
复选框和上面所有表单元素都不一样:
要靠checked={}得到值,要判断includes
要写一个函数,传入将要验证的值,根据这个值是不是已经在数组中,再决定删除、添加。
setF(word){
if(this.state.f.includes(word)){
//如果这个值已经在f数组中,则删除
this.setState({
f : this.state.f.filter(item=>item != word)
});
}else{
//如果这个值不在f数组中,则加入数组
this.setState({
f : [...this.state.f, word]
});
}
}
<p>
爱好:
<input
type="checkbox"
value="看书"
checked={this.state.f.includes('看书')}
onChange={(e) => { this.setF("看书") }}
/>看书
<input
type="checkbox"
value="游泳"
checked={this.state.f.includes('游泳')}
onChange={(e) => { this.setF("游泳") }}
/>游泳
<input
type="checkbox"
value="打球"
checked={this.state.f.includes('打球')}
onChange={(e) => { this.setF("打球") }}
/>打球
<span>【{this.state.f.join(',')}】</span>
</p>
3.5可选择的表单类-案例
这个案例学习DOM的上下树
控制元素是否显示或隐藏,不要用display属性
而是用三元运算符控制DOM元素是否上下树。如果上数写标签,如果不上树写null
不管是做什么案例,都是两大部分:①、写JSX侵入DOM标签,②、事件监听,改变state
import React from "react";
export default class App extends React.Component{
// 构造函数
constructor(){
super();
this.state = {
arr:[
{ "id": 1, "name": "小明", "age": 12, "sex": "男" },
{ "id": 2, "name": "小红", "age": 13, "sex": "女" },
{ "id": 3, "name": "小刚", "age": 14, "sex": "男" },
{ "id": 4, "name": "小白", "age": 15, "sex": "男" }
],
showCols:['姓名',"年龄","性别"]
}
}
setChangeCols(word){
if(this.state.showCols.includes(word)){
this.setState({
showCols:this.state.showCols.filter(item=>item !=word)
})
}else{
this.setState({
showCols:[ ...this.state.showCols, word ]
});
};
}
render(){
return <div>
<div>
<input type="checkbox"
value="姓名"
checked={this.state.showCols.includes("姓名")}
onChange={(e)=>{this.setChangeCols("姓名")}}
/>姓名
<input type="checkbox"
value="年龄"
checked={this.state.showCols.includes("年龄")}
onChange={(e)=>{this.setChangeCols("年龄")}}
/>年龄
<input type="checkbox"
value="性别"
checked={this.state.showCols.includes("性别")}
onChange={(e)=>{this.setChangeCols("性别")}}
/>性别
</div>
<span>{this.state.showCols}</span>
<table>
<tbody>
<tr>
<th>ID</th>
{this.state.showCols.includes("姓名") ? <th>姓名</th> : null}
{this.state.showCols.includes("年龄") ? <th>年龄</th> : null}
{this.state.showCols.includes("性别") ? <th>性别</th> : null}
</tr>
{
this.state.arr.map(item=>{
return <tr key={item.id}>
<td>{item.id}</td>
{this.state.showCols.includes("姓名") ? <td>{item.name}</td>:null}
{this.state.showCols.includes("年龄") ? <td>{item.age}</td>:null}
{this.state.showCols.includes("性别") ? <td>{item.sex}</td>:null}
</tr>
})
}
</tbody>
</table>
</div>
}
};
3.6三级联动-案例
额外提供的数据:
[
{
"name" : "广东省",
"city" : [
{
"name":"广州",
"area":[
"天河区",
"白云区",
...
]
},
{
"name":"深圳",
"area":[
"福田区",
"南山区"
]
}
....
]
},
....
]
示例代码
forEach、filter、map、reduce函数都是表达式,而不是语句体。if和for语句是语句体。
import React from "react";
import city from "./city.js";
export default class App extends React.Component{
constructor(){
super();
this.state = {
"province":"广东省",
"city":"广州市",
"area":"天河区"
}
}
render(){
//循环遍历省份
const showProvinces = ()=>{
//遍历city数据,提取每一项省份成为option
var arr=[];
city.forEach((item,index)=>{
arr.push(<option key={index} value={item.name}>{item.name}</option>)
});
return arr;
}
//循环遍历市,显示哪一个城市,需要根据state的province属性的省份选择城市
const showCitys = ()=>{
var arr = [];
// 先筛选省份,再次筛选对应城市列表
var cityArr = city.filter(item=>item.name == this.state.province)[0].city;
cityArr.forEach((item,index)=>{
arr.push(<option key={index} value={item.name}>{item.name}</option>)
});
return arr;
};
//显示区县
const showAreas = ()=>{
var arr = [];
// 先筛选省份,再次筛选对应城市列表
var cityArr = city.filter(item=>item.name == this.state.province)[0].city;
//根据市,得出区,只需选择区的第一项
var areaArr = cityArr.filter(item => item.name == this.state.city)[0].area
//最后根据筛选出的市,遍历区
areaArr.forEach((item,index)=>{
arr.push(<option key={index} value={item}>{item}</option>)
})
return arr;
}
return <div>
省份:<select
value={this.state.province}
onChange={(e)=>{
this.setState({
"province":e.target.value,
"city":city.filter(item=>item.name == e.target.value)[0].city[0].name
})
}}
>
{showProvinces()}
</select>
城市:<select
value={this.state.city}
onChange={(e)=>{
this.setState({"city":e.target.value})
}}
>
{showCitys()}
</select>
区县:<select
value={this.state.area}
onChange={(e)=>{
this.setState({"area":e.target.value})
}}
>
{showAreas()}
</select>
</div>
}
};