前端笔记之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> } }
注意:
react中事件监听,要写在标签上。
事件名onclick而不是onclick,注意大写字母。因为react将事件名都进行了拓展,所以onclick是react自己的方法。同理,所有事件名on后面的首字母都是大写:onmouseenter、ondoubleclick、onkeydown。
onclick=后面紧跟{},表示插值。大括号中是一个箭头函数,这个函数必须是箭头函数,否则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> } };