dangerouslySetInnerHTML
React写进{}内的东西,不允许被当作代码块解析,是为了防止xss攻击和代码注入
XSS(跨站脚本攻击,Cross-Site Scripting) 是一种常见的安全漏洞,攻击者通过注入恶意脚本到网页中,从而在用户浏览器中执行恶意代码。XSS 攻击通常发生在 Web 应用程序中,尤其是当用户输入的内容未经过滤或转义就直接显示在页面上时。
代码注入:恶意用户可以通过注入 HTML 或 JavaScript 代码,篡改页面内容或窃取用户数据
不过如果你想忽略跨站脚本攻击也是可以的
dangerouslySetInnerHTML 是React 中用于直接设置 HTML 内容的一个特殊属性。它的名字中的 "dangerously" 暗示了它的潜在风险:如果使用不当,可能会导致 XSS(跨站脚本攻击) 等安全问题
class App extends Component{a=100myref = React.createRef()//返回一个ref对象state = {list: [{id:1,text:'111'},{ id: 2, text: '222' },{id:3,text:'333'}]
}render(){return (<div><input ref={this.myref} />{/* //把ref绑定在input上 */}<button onClick={this.handleClick}>add</button><ul>{
this.state.list.map((item, index) => <li key={item.id}><span dangerouslySetInnerHTML={{ __html: item.text }}></span>
<button onClick={() => this.handleDel(index)}>delete</button></li>)
}</ul><div className="hidden">暂无待办事项</div></div>)}
只有你很信任后端给的数据才可以这么写哦
或者是后端ajax返回的数据可以直接被渲染也可以这么用
影院案例
使用猫的眼睛的电影对外的API访问数据
constructor() {super()this.state = {cinemaList: [],bakcinemaList:[]}//用axios请求数据axios.get('https://apis.netstart.cn/maoyan/index/moreCinemas?day=2021-11-12&offset=0&limit=20&districtId=-1&lineId=-1&hallType=-1&brandId=-1&serviceId=-1&areaId=-1&stationId=-1&item&updateShowDay=true&reqId=1636710166221&cityId=1&lat=23.135636443326355&lng=1').then(res => {console.log(res)this.setState({cinemaList: res.data,bakcinemaList: res.data})}).catch(err => {console.log(err)})}
再用map将数组元素转化为标签进行渲染
{this.state.cinemaList.map(item => <dl key={item.cinemaId}><dt>{ item.title}</dt><dd>{ item.location}</dd></dl>)}
加上样式:
*{margin: 0;padding:0;
}
ul{list-style: none;display:flex;position: fixed;bottom:0px;left:0px;height: 50px;line-height:50px;width:100%;background-color:white ;
}
ul li{flex:1;text-align: center;
}
.active{color:red;
}
dl{height:50px;border-bottom:1px solid grey;
}dl dt{font-size: 20px;
}
dl dd{font-size: 12px;color: grey;
}
input{
width: 100%;
height: 30px;
line-height: 30px;
font-size: medium;
}
记得导入样式
当触发事件的元素和ref为一个元素时,可以利用事件对象e代替ref
所以在这个模糊搜索功能处,可以用target.value获取input输入框的值,然后使用filter筛选标题和地址满足要求的影院
很简单的小项目捏
import React from 'react'
import axios from 'axios'
class Cinema extends React.Component{constructor() {super()this.state = {cinemaList: [],bakcinemaList:[]}//用axios请求数据axios.get('https://apis.netstart.cn/maoyan/index/moreCinemas?day=2021-11-12&offset=0&limit=20&districtId=-1&lineId=-1&hallType=-1&brandId=-1&serviceId=-1&areaId=-1&stationId=-1&item&updateShowDay=true&reqId=1636710166221&cityId=1&lat=23.135636443326355&lng=1').then(res => {console.log(res)this.setState({cinemaList: res.data,bakcinemaList: res.data})}).catch(err => {console.log(err)})}//生命周期函数更适合发送ajax请求render() {return (<div><input onChange={this.handleInput}/>{this.state.cinemaList.map(item => <dl key={item.cinemaId}><dt>{ item.title}</dt><dd>{ item.location}</dd></dl>)}</div> )}handleInput = (event) => {//感觉这里可以加一个防抖console.log('input', event.target.value)let newList = this.state.bakcinemaList.filter(item => item.title.includes(event.target.value)||item.location.includes(event.target.value))this.setState({cinemaList:newList})}
}
export default Cinema
setState在类组件里也同样是异步的
handleClick = () => {this.setState({count:this.state.count+1})console.log(this.state.count)this.setState({count:this.state.count+1})console.log(this.state.count)this.setState({count:this.state.count+1})console.log(this.state.count)}
}
setState处在同步的环境中,异步更新状态,异步更新真实dom,一个事件循环彻底结束后,在下一轮宏任务中更新状态
在react18以前,setState在异步的情况下是同步的,不过现在React 18 及之后:引入了自动批处理,即使在异步代码中,setState
也可能表现为异步。
useState
的更新既不是宏任务,也不是微任务,而是由 React 的调度机制管理。
setState是改变状态的,你怎么知道他什么时候改变完呢?
setState的第二个参数可以传回调函数,获取的状态是当前作用域内的state,也就是说:
handleClick2 = () => {setTimeout(() => {this.setState({count:this.state.count+1},()=> console.log('setState的第二个参数',this.state.count))console.log('外部的state',this.state.count)},0)}
这个第二个参数里的回调函数,是在状态和dom都更新完以后才执行的
BetterScroll
让页面更平滑的滚动
我个人感觉这更像是对setState第二个参数的妙用,因为这个回调函数会在状态和dom都完成更新后再执行
例如BetterScroll本身可以更平滑的滚动,但是要想把数据全部包在wraper里,就要在dom树和状态都更新完以后,再创建wrapper实例:
import React, { Component } from "react"
import BetterScroll from 'better-scroll'
class App extends Component{state={
list:[]}render(){return (<div><button onClick={()=>this.getData()}>click</button><div className='wrapper' style={{height:'200px',background:'yellow',overflow:'hidden'}}><ul className='content' >{this.state.list.map(item => <li key={item}>{item}</li>)}</ul></div></div>)}getData() {let list = [1, 2, 3, 4, 5, 6, 7, 8,9,10,11,12,13]this.setState({list:list}, () => { new BetterScroll('.wrapper') })}
}
export default App
把影院组件套到BetterScroll上来,一定要满足以下结构:
return (<div><input onChange={this.handleInput}/><div className='wrapper' style={{height:'500px',background:'yellow',overflow:'hidden'}}><div className='content'>{this.state.cinemaList.map(item => <dl key={item.cinemaId}><dt>{ item.title}</dt><dd>{ item.location}</dd></dl>)}</div></div></div> )
kerwin老师说可以不放在setState的第二个参数上也可以执行,我试了试react18不行哦
//用axios请求数据axios.get('https://apis.netstart.cn/maoyan/index/moreCinemas?day=2021-11-12&offset=0&limit=20&districtId=-1&lineId=-1&hallType=-1&brandId=-1&serviceId=-1&areaId=-1&stationId=-1&item&updateShowDay=true&reqId=1636710166221&cityId=1&lat=23.135636443326355&lng=1').then(res => {console.log(res)this.setState({cinemaList: res.data,bakcinemaList: res.data},()=>{new BetterScroll('.wrapper') })//貌似react18必须放在这个位置}).catch(err => {console.log(err)})
状态是react里很重要的一种数据挂载的方式捏
属性
属性之于标签就像参数之于函数,属性是父组件传过来的:
import React, { Component } from "react"
import Navbar from "./base/Navbar"
class App extends Component{render(){return (<div><div><h2>首页</h2><Navbar tittle='首页'/></div><div><h2>列表</h2><Navbar tittle='列表'/></div><div><h2>购物车</h2><Navbar tittle='购物车'/></div></div>)}
}
export default App
通过this.props获取属性
渲染到网页上去:
import React, { Component } from "react"
class Navbar extends Component{state = {//只能内部自己用,外部无法改变
}//属性是父组件传来的,通过this.props获取render() {console.log(this.props)let {tittle}=this.propsreturn (<div>Navbar-{tittle}</div>)}
}
export default Navbar
这样我们就实现了对属性的键值对使用,在组件上通过key=value 写属性,通过this.props获取属性,这样组件的可复用性提高了。
还可以根据属性进行条件渲染
false和true要放在{}里才会被当作boolean处理,不然就是字符串,只要字符串不为空怎么判断都是true
props.js
import React, { Component } from "react"
import Navbar from "./base/Navbar"
class App extends Component{render(){return (<div><div><h2>首页</h2><Navbar tittle='首页' leftshow={false}/></div><div><h2>列表</h2><Navbar tittle='列表' leftshow={true}/></div><div><h2>购物车</h2><Navbar tittle='购物车' leftshow={true}/></div></div>)}
}
export default App
index.js
import React, { Component } from "react" class Navbar extends Component{state = {//只能内部自己用,外部无法改变
}//属性是父组件传来的,通过this.props获取render() {console.log(this.props)let {tittle,leftshow}=this.propsreturn (<div>{leftshow &&<button>返回</button>}Navbar-{tittle}<button>home</button></div>)}
}
export default Navbar
首页模块无法返回,所以不显示左按钮
验证属性
在接收属性的时候要做验证
类属性:定义在类本身上的属性,而不是类的实例上的属性。它们通常用于存储与类相关的共享数据或方法。在es7里为了区分类属性和对象属性,在前面加一个关键字static,所以类属性也叫静态属性
对象属性:定义在类的实例上的属性,每个实例都有自己独立的对象属性。要想访问对象属性,就必须先创建实例
class Test{a = 1//对象属性static a=100//类属性
}
let obj = new Test()
console.log('类属性',Test.a,'对象属性:',obj.a)
做好对属性封装的验证,才能知道你用的属性对不对:
import React, { Component } from "react"
import AugustTypes from 'prop-types'console.log(AugustTypes)class Navbar extends Component {state = {//只能内部自己用,外部无法改变}//属性是父组件传来的,通过this.props获取render() {console.log(this.props)let { title, leftshow } = this.propsreturn (<div>{leftshow && <button>返回</button>}Navbar-{title}<button>home</button></div>)}}//类属性Navbar.propTypes = {title: AugustTypes.string,leftshow:AugustTypes.bool}export default Navbar
添加默认属性
可以这样写在外面
也可以加上static关键字写在类里面:
import React, { Component } from "react"
import AugustTypes from 'prop-types'console.log(AugustTypes)class Navbar extends Component {state = {//只能内部自己用,外部无法改变}//类属性static propTypes = {title: AugustTypes.string,leftshow:AugustTypes.bool}//默认属性static defaultProps = {leftshow:true
}//属性是父组件传来的,通过this.props获取render() {console.log(this.props)let { title, leftshow } = this.propsreturn (<div>{leftshow && <button>返回</button>}Navbar-{title}<button>home</button></div>)}}
export default Navbar
属性注意事项
可以利用对象的解构赋值来添加属性,或者说属性也可以写成一个对象:
import React, { Component } from "react"
import Navbar from "./base/Navbar"
class App extends Component{render() {let obj = {title: 'test',leftshow:false}return (<div><Navbar title={obj.title} leftshow={obj.leftshow}/><Navbar {...obj} /></div>)}
}
export default App
效果是一样的
在函数式组件里props是函数的形参,在使用的时候解构就可以,之前学过这里就不写了
状态和属性的区别
相似点:都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)
不同点:
1. 属性能从父组件获取,状态不能
2. 属性可以由父组件修改,状态不能
3. 属性能在内部设置默认值,状态也可以,设置方式不一样
4. 属性不在组件内部修改,状态要在组件内部修改
5. 属性能设置子组件初始值,状态不可以
6. 属性可以修改子组件的值,状态不可以
state 的主要作用是用于组件保存、控制、修改自己的可变状态。 state 在组件内部初始化,可以被 组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制 的数据源。 state 中状态可以通过 this.setState 方法进行更新, setState 会导致组件的重新渲染。
props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参 数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props ,否则组件的 props 永远保持不变(算是一种单向数据流)。父子通信本身也是子向父发出请求,父再修改属性传给子,属性是只读的:子组件不能直接修改父组件传递的属性。
没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件 (stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
状态可以被当作属性值传递给子组件