一、jsx语法规则
jsx语法规则:
- 定义虚拟DOM时,不要写引号
- 标签中混入JS表达式要用{}
- 样式的类名指定不要用class,要用className
- 内联样式,要用styles={{key:value的样式去写}}
- 只有一个跟标签
- 标签需要闭合
- 标签首字母
(1)若小写字母开头,则将标签转为html中同名元素,若html中无该标签对应的同名元素,则报错
(2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,就报错
二、函数式组件
在没有脚手架的情况下,需要引入相关的react的包
React相关的文件:
react.development.js —— React核心文件
react-dom.development.js —— React用来操作DOM的文件
babel.min.js —— 在React中会使用到jsx,但是浏览器不支持jsx,所以使用babel将jsx编译为js
<!--引入react核心库 核心库必须最先引入-->
<script type="text/javascript" src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<!--引入react拓展库,react-dom,支持react操作dom-->
<script type="text/javascript" src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!--引入babel,用于将jsx转换为js-->
<script type="text/javascript" src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<div id="test"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 创建函数式组件
function MyComponent () {
return <h2>我是函数式组件定义的㢟(简单组件)的定义</h2>
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
</script>
三、类组件
<div id="test"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 创建类式组件
class MyComponent extends React.Component{
// render 放在类的原型对象上,供实例使用,this指的是MyComponent组件的实例对象
render() {
return (
<h2>我是类组件</h2>
)
}
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>...)之后,发生了什么
1.React解析组件标签,找到MyComponent组件
2.发现组件是使用类定义的,随后new出来该类的实例,是通过该实例调用到原型上的render方法
3.将render返回的虚拟DOM转化为真是DOM,随后呈现在页面中
*/
四、组件三大核心属性
1.state
<div id="test"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 创建类式组件
class Weather extends React.Component{
// 构造器调用一次
constructor (props) {
super(props)
this.state = {
isHot: true
}
this.changeWeather = this.changeWeather.bind(this)
}
// render调用1+n次 1是初始化那次,n是状态更新的次数
render() {
const {isHot} = this.state
return (
<h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热': '凉爽'}</h2>
)
}
changeWeather () {
// changeWeather放在那里?Weather的原型对象上,供实例使用
// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用的
// 类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
// this.state.isHot = !this.state.isHot(错误写法)
// 状态(state)不可直接更改,必须通过setState修改,且更新是一种合并,不是合并
this.setState({
isHot: !this.state.isHot
})
}
}
// 渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
state简写方式:
<div id="test"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 创建类式组件
class Weather extends React.Component{
state = {
isHot: true
}
render() {
const {isHot} = this.state
return (
<h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热': '凉爽'}</h2>
)
}
// 自定义方法-要用赋值语句的形式+箭头函数
changeWeather = () => {
this.setState({
isHot: !this.state.isHot
})
}
}
// 渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
2.props
构造器是否接受props,是否传递给super,取决于是否希望在构造器中通过this访问props
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<!-- PropTypes -->
<script type="text/babel">
// 创建类式组件
class Person extends React.Component{
// props做限制
render () {
const {name, age, sex} = this.props
// prop是只读的
return (
<ul>
<li>姓名: {name}</li>
<li>性别:{sex}</li>
<li>年龄: {age+1}</li>
</ul>
)
}
}
// 对标签属性进行类型,必要性的限制
Person.propTypes = {
name: PropTypes.string.isRequired, // 限制name必传,且为字符串
sex: PropTypes.string, // 限制sex为字符串
age: PropTypes.number, // 限制age为数值
speak: PropTypes.func // 限制speak为函数
}
// 指定默认标签属性
Person.defaults = {
sex: '不男不女',
age: 18
}
// 渲染组件到页面,单传递
ReactDOM.render(<Person name="jerry" speak='2' age={19} sex="女"/>, document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>, document.getElementById('test2'))
// ReactDOM.render(<Person name="laowang" age="18" sex="女"/>, document.getElementById('test3'))
// 多传递
const p = {name: 'laowang', age: 18, sex: '女'}
// 因为babel以及React库,所以可以直接使用...p,不然对于对象会报错
ReactDOM.render(<Person {...p}/>, document.getElementById('test3'))
</script>
简写props,将类外的移到类内,并且在险种语句前面加static
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<!-- PropTypes -->
<script type="text/babel">
// 创建类式组件
class Person extends React.Component{
// 对标强属性进行类型,必要性的限制
static propTypes = {
name: PropTypes.string.isRequired, // 限制name必传,且为字符串
sex: PropTypes.string, // 限制sex为字符串
age: PropTypes.number, // 限制age为数值
speak: PropTypes.func // 限制speak为函数
}
// 指定默认标签属性
static defaults = {
sex: '不男不女',
age: 18
}
render () {
const {name, age, sex} = this.props
// prop是只读的
return (
<ul>
<li>姓名: {name}</li>
<li>性别:{sex}</li>
<li>年龄: {age+1}</li>
</ul>
)
}
}
// 渲染组件到页面,单传递
ReactDOM.render(<Person name="jerry" speak='2' age={19} sex="女"/>, document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>, document.getElementById('test2'))
// ReactDOM.render(<Person name="laowang" age="18" sex="女"/>, document.getElementById('test3'))
// 多传递
const p = {name: 'laowang', age: 18, sex: '女'}
// 因为babel以及React库,所以可以直接使用...p,不然对于对象会报错
ReactDOM.render(<Person {...p}/>, document.getElementById('test3'))
</script>
函数式组件使用props
// 函数式组件使用props
function Person (props) {
const {name, age, sex} = props
return (
<ul>
<li>姓名: {name}</li>
<li>性别:{sex}</li>
<li>年龄: {age+1}</li>
</ul>
)
}
3.refs与数据处理
<div id="test1"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<!-- PropTypes -->
<script type="text/babel">
/*
事件处理函数
(1)通过onXXX属性指定事件处理函数, 注意大小写
React使用的是自定义(合成)事件,而不是使用的原生DOM事件 为了更好的兼容性
React是通过事件委托的方式处理的 为了高效
(2)通过event.target得到发生事件的DOM元素对象
*/
// refs
// 创建类式组件
class Person extends React.Component{
// 这种情况可能会弃用,直接使用refs
showData = () => {
// 第一种
// const {input1} = this.refs
// 第二种
const {input1} = this
}
showData2 = () => {
// 第一种
// const {input2} = this.refs
// 第二种
const {input2} = this
}
saveInput =(a) => {
this.input1 = a
}
// 回调函数式的ref
// 创建Refs
/* 调用后可以返回一个容器。该容器可以存储呗ref所表示的节点
*/
myRef = React.createRef()
showData1 =() => {
alert(this.myRef.current.value)
}
render () {
return (
<div>
{/*// 第一种*/}
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
{/*第二种 回调函数中执行的次数,初始化调取一次, 更新的时候每次会调取两次,这种较常用*/}
{/*<input ref={(currentNode) => {this.input1 = currentNode}} type="text" placeholder="点击按钮提示数据"/>*/}
{/*这种避免调用两次*/}
{/*<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据"/>*/}
{/*// 第三种*/}
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
{/*<button onClick={this.showData}>点我提示左侧数据</button>*/}
<button onClick={this.showData1}>点我提示左侧数据</button>
{/*<input onBlur={this.showData2} ref="input2" type="text" placeholder="失去焦点提示数据"/>*/}
<input onBlur={this.showData2} ref={c => {this.input2 = c}} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Person/>, document.getElementById('test1'))
</script>
五、受控组件与非受控组件
<div id="test1"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<!-- PropTypes -->
<script type="text/babel">
class Login extends React.Component{
handleSubmit = (event) => {
// 阻止默认事件,不让其自动刷新
event.preventDefault()
// const {username, password } = this
// alert("用户名是"+username.value+"密码是:"+password.value)
}
state={
username: '',
password: ''
}
saveUsername = (event) => {
this.setState({
username:event.target.value
})
}
savePassword = (event) => {
this.setState({
password:event.target.value
})
}
/*
高阶函数:如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数
1.若A函数,接受的参数是一个函数,那么A就可以称之为高阶函数
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
常见的高阶函数:Promise、定时器
函数柯里化:通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式
function sum(a) {
return (b) => {
return (c) => {
return a+b+c
}
}
}
sum(1)(2)(3)
*/
// 保存表单数据到状态中
saveFormData = (dataType) => {
return (event) => {
console.log(event.target);
this.setState({
[dataType]: event.target.value
})
}
}
render () {
return (
<form onSubmit={this.handleSubmit}>
{/*输入类的属于非受控组件现用现取*/}
{/*用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>*/}
{/*输入类的属于需要放到state,并从state维护状态*/}
{/*用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>登录</button>*/}
{/*高阶函数,函数柯里化*/}
用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test1'))
</script>
不使用柯里化
saveFormData = (dataType,event) => {
this.setState({
[dataType]: event.target.value
})
}
render () {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={(event) => {this.saveFormData('username', event.target.value)}} type="text" name="username"/>
密码:<input onChange={(event) => {this.saveFormData('password', event.target.value)}} type="password" name="password"/>
<button>登录</button>
</form>
)
}
五、生命周期
1.生命周期(旧)
(1)引入生命周期
想要一开始定时器就触发,但是定时器放在render()里,发生了递归,render初始化第一次调用,每修改一次state一直调用;但是如果放在action中不能一上来就开启定时器,
<div id="test"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Life extends React.Component{
state = {
opacity: 1
}
detach = () => {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 这么做不能一上来就开启定时器,而是点击的时候才能开启定时器
action = () => {
// 发生了递归,render初始化第一次调用。每修改一次state一直调用
setInterval(() => {
// 获取原状态
let {opacity} = this.state
// 减小0.1
opacity -= 0.1
if (opacity <= 0) {
opacity = 1
}
// 设置新的透明度
this.setState({
opacity
})
}, 200)
}
render(){
/*// 发生了递归,render初始化第一次调用。每修改一次state一直调用
setInterval(() => {
// 获取原状态
let {opacity} = this.state
// 减小0.1
opacity -= 0.1
if (opacity <= 0) {
opacity = 1
}
// 设置新的透明度
this.setState({
opacity
})
}, 200)
*/
return(
<div>
<h1 style={{opacity:this.state.opacity}}>React学不会怎么办</h1>
<button onClick={this.detach}>不活了</button>
<button onClick={this.action}>开始变化</button>
</div>
)
}
}
ReactDOM.render(<Life/>, document.getElementById('test'))
</script>
解决上述问题
<div id="test"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 生命周期回调函数 《=》生命周期钩子函数=》生命周期函数=声明周期钩子
class Life extends React.Component{
state = {
opacity: 1
}
detach = () => {
// 清除定时器
// clearInterval(this.timer)
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 组件挂载完毕
componentDidMount () {
this.timer = setInterval(() => {
// 获取原状态
let {opacity} = this.state
// 减小0.1
opacity -= 0.1
if (opacity <= 0) {
opacity = 1
}
// 设置新的透明度
this.setState({
opacity
})
}, 200)
}
// 组件将要卸载
componentWillUnmount () {
// 清除定时器
clearInterval(this.timer)
}
// render调用的时机,初始化渲染,状态更新之后
render(){
return(
<div>
<h1 style={{opacity:this.state.opacity}}>React学不会怎么办</h1>
<button onClick={this.detach}>不活了</button>
</div>
)
}
}
ReactDOM.render(<Life/>, document.getElementById('test'))
</script>
图片表示生命周期图
react版本18.0.0的需要加UNSAFE_,UNSAFE_componentWillUpdate、UNSAFE_componentWillReceiveProps、UNSAFE_componentWillMount,才能正常工作
1.初始化阶段:由ReactDOM.render()触发 ---初次渲染
1.constructor
2.componentWillMount()
3.render()
4.componentDidMount() ===常用
一般在这个钩子中做一些初始化的事情,比如开启定时器。发送网络请求等
2.更新阶段:由组件内部this.setState()或父组件render触发
1.shouldComponentUpdate()
2.componentWillUpdate()
3.render() ==必须使用的一个
4.componentDidUpdate()
3.卸载组件:由React.DOM.unmountComponentAtNode()触发
1.componentWillUnmount() ==常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息等
挂载时:
setState时:
forceUpdate时
父组件render
class A extends React.Component{
state = {
carName: '奔驰'
}
changeCar = () => {
this.setState({
carName: '奥拓'
})
}
render () {
return (
<div>
<div>A</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
class B extends React.Component{
// 组件将要接受新的props的钩子
UNSAFE_componentWillReceiveProps(props) {
console.log('B--componentWillReceiveProps',props);
}
// 组件是否被更新,更新组件的阀门
shouldComponentUpdate() {
console.log('B --shouldComponentUpdate');
return true
}
// 组件将要更新
UNSAFE_componentWillUpdate () {
console.log('B --componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate() {
console.log('B --componentDidUpdate')
}
render () {
console.log('B--render');
return (
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
2.生命周期(新)
旧的和新的差别,废弃三个生命周期函数,同时提出两个新的生命周期函数
// state的值在任何的时候取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state) {
console.log('count--getDerivedStateFromProps', props,state);
// return props
return null
}
// 在更新之前获取快照
getSnapshotBeforeUpdate() {
console.log('count--getSnapshotBeforeUpdate');
// 需要返回一个快照值不然会报警告
return '1'
}
// 组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue) {
console.log('Count --componentDidUpdate', preProps,preState,snapshotValue)
}
getSnapshotBeforeUpdate的使用场景:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.list{
width: 200px;
height: 150px;
background-color: skyblue;
overflow: auto;
}
.news{
height:30px;
}
</style>
</head>
<body>
<div id="test"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class NewsList extends React.Component{
state = {
newsArr: []
}
componentDidMount() {
setInterval(() => {
// 获取原状态
const {newsArr} = this.state
// 模拟一条新闻
const news = '新闻'+(newsArr.length + 1)
// 更新状态
this.setState({
newsArr: [news, ...newsArr]
})
},1000)
}
getSnapshotBeforeUpdate() {
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps,preState,height) {
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render () {
return (
<div className="list" ref="list">
{
this.state.newsArr.map((n,index)=> {
return <div className="news" key={index}>{n}</div>
})
}
</div>
)
}
}
ReactDOM.render(<NewsList/>, document.getElementById('test'))
</script>
</body>
</html>
六、虚拟DOM和Diffing算法
验证diffing算法
<div id="test"></div>
<!-- // 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- // 用于支持DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- // 引入babel 将jsx转换成js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Time extends React.Component{
state = {
date: new Date()
}
componentDidMount() {
setInterval(() => {
this.setState({
date: new Date()
})
},1000)
}
render () {
return (
<div>
<h1>Hello</h1>
<input type="text"/>
<span>现在是:{this.state.date.toTimeString()}</span>
</div>
)
}
}
ReactDOM.render(<Time/>, document.getElementById('test'))
</script>
1.虚拟DOM中key的作用
当状态中的数据发生变化时,react会更具【新数据】生成【新的虚拟DOM】
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a.旧虚拟DOM中找到了与虚拟DOM相同的key
(1)若虚拟DOM中内容没变,直接使用之前的真实DOM
(2)若虚拟DOM中内容变了,则生成新的真实DOM,随后持续换掉页面中之前的真实DOM
b.旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到页面
2.用index作为key可能引发的问题:
(1) 若对数据进行:逆序添加,逆序删除等破坏顺序操作
会产生没有必要的真实DOM更新==界面效果没问题,但效率低
(2)如果结构中还包含输入类的DOM:
会产生错误DOM更新==界面有问题
(3)注意:如果不存在对数据的逆序添加、删除等破坏顺序操作
仅用于渲染列表用于展示。使用index作为key是没有问题的
3.开发中如何选择key?
(1)最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等
(2)如果确定只是简单的展示数据,用index也是可以的。