- 函数式组件适用于‘简单组件’的定义;
- 类式组件适用于‘复杂组件’的定义。
- 有状态(state)的就是复杂组件,
- 反之则是简单组件。
- 实例:类似于java中的实例对象。
(最新版本的react新增一个hooks属性,可以让函数式组件拥有三大属性)
1.0 state
- state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
- 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>hello_react</title>
</head>
<!-- 1.0 准备容器 -->
<div id="test"></div>
<!-- 2.0引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 3.0 引入react-dom,用于支持react操作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>
<body>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render() {
return (
<h1>
今天天气很炎热
</h1>
);
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
</body>
</html>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//构造器调用几次? ———— 1次
constructor(props) {
super(props);
this.state= {isHot:false,wind:'微风'};
}
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render() {
console.log(this);
return <h1>今天天气很{this.state.isHot ? '炎热' : '凉爽'},{this.state.wind}</h1>;
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
这里的props下一节解释,可以去官网看首页就有演示。
2.0 react绑定事件
首先看原生的js绑定事件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button onclick="demo()">按钮3</button>
<script type="text/javascript" >
const btn1 = document.getElementById('btn1')
btn1.addEventListener('click',()=>{
alert('按钮1被点击了')
})
const btn2 = document.getElementById('btn2')
btn2.onclick = ()=>{
alert('按钮2被点击了')
}
function demo(){
alert('按钮3被点击了')
}
</script>
</body>
</html>
react的事件绑定:
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//构造器调用几次? ———— 1次
constructor(props) {
super(props);
this.state = { isHot: false, wind: "微风" };
}
render() {
return (
<h1 onClick={changeWeather}>
今天天气很{this.state.isHot ? "炎热" : "凉爽"},{this.state.wind}
</h1>
);
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
function changeWeather() {
console.log("被点击了!");
}
</script>
3.0 类中方法中的this
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//构造器调用几次? ———— 1次
constructor(props) {
super(props);
this.state= {isHot:false,wind:'微风'};
//解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render() {
console.log(this);
return <h1 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热' : '凉爽'},{this.state.wind}</h1>;
}
//changeWeather调用几次? ———— 点几次调几次
changeWeather(){
//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
console.log('changeWeather');
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot:!isHot})
console.log(this);
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
点击“凉爽”,自动变为“炎热”
严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
4.0 state的简写方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>state简写方式</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作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">
//1.创建组件
class Weather extends React.Component{
//初始化状态
state = {isHot:false,wind:'微风'}
render(){
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//自定义方法————要用赋值语句的形式+箭头函数
changeWeather = ()=>{
const isHot = this.state.isHot
this.setState({isHot:!isHot})
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
</body>
</html>
5.0 props
解决从组件外部将参数/属性/函数, 带入组件中去。
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age}</li>
</ul>
)
}
}
//渲染组件到页面
const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test'))
</script>
或者如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>hello_react</title>
</head>
<!-- 1.0 准备容器 -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 2.0引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 3.0 引入react-dom,用于支持react操作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>
<body>
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
ReactDOM.render(<Person name="李四" age={30} sex="女"/>,document.getElementById('test3'))
</script>
</body>
</html>
如上编写已经很接近实际开发,但还是存在如下问题:
- 新的信息得不断重复插入真实DOM的语句。
- 一般的数据填充是ajax从服务器请求,而不是在前端代码明确。
6.0 props批量传递
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
// 通过结构复制的方式从中拿到值
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
// console.log('@',...p);
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
</script>
...p
三点运算符。
回顾一下js中的三点运算符。
<script type="text/javascript" >
let arr1 = [1,3,5,7,9]
let arr2 = [2,4,6,8,10]
console.log(...arr1); //展开一个数组
let arr3 = [...arr1,...arr2]//连接数组
console.log(...arr3 ); //展开一个数组
</script>
展开数据并分别打印或者连接数组。
在函数中使用,函数传参:
<script type="text/javascript" >
//在函数中使用
function sum(...numbers){
return numbers.reduce((preValue,currentValue)=>{
return preValue + currentValue
})
}
console.log(sum(1,2,3,4));
</script>
这一点和java中的三点运算符意义一致。
以下代码无法运行:
//构造字面量对象时使用展开语法
let person = {name:'tom',age:18}
console.log(...person); //报错,展开运算符不能展开对象
但如下写法可以:
<script type="text/javascript" >
//构造字面量对象时使用展开语法
let person = {name:'tom',age:18}
let person2 = {...person}//复制一个对象,新语法,自变量的形式复制一个对象
person.name = 'jerry'
console.log(person2);
console.log(person2.name);
console.log(person);
</script>
如上语法知识课参考如下网站:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference
<script type="text/javascript" >
//构造字面量对象时使用展开语法
let person = {name:'tom',age:18}
let person2 = {...person}
//console.log(...person); //报错,展开运算符不能展开对象
person.name = 'jerry'
console.log(person2);
console.log(person);
//合并
let person3 = {...person,name:'jack',address:"地球"}
console.log(person3);
</script>
回到react,{...p}
中的花括号只是作为一个分隔符在使用,和如上js中的花括号不是一个意思。相当于只写...p
。
之所以在react能用这个语法展开原生不能展开的对象,在于recat支持了这种展开方式。
但不能随意使用,只能用于标签属性的传递。
6.0 props限制
需求:显示的年龄要比传入的年龄大一岁。
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
</script>
此代码存在一个问题:
当自己写的时候 知道传参是一个int类型的年龄,但如果是其他人接手,可能就会出现传参错误,变成字符串拼接。
综合来看,我们有必要有以下限制需求:
- 限制某些属性是不可或缺的,必须传参
- 对传递的标签属性进行类型限制
3.给某些属性提供默认值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>对props进行限制</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作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>
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
// console.log(this);
const {name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
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.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name={100} speak={speak}/>,document.getElementById('test1'))//会报错
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
function speak(){
console.log('我说话了');
}
</script>
</body>
</html>
引入prop-types.js包。在recat中引入包相当于一个js引入了一个关键对象。
同时,props是只读的。
7.0 props的简写
<script type="text/babel">
//创建组件
class Person extends React.Component{
constructor(props){
//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
// console.log(props);
super(props)
console.log('constructor',this.props);
}
//对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
render(){
// console.log(this);
const {name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
</script>
构造函数的知识点参考官网文档:
https://zh-hans.reactjs.org/docs/react-component.html#constructor
构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props(此知识点实际开发中基本用不到,实际开发中基本不写构造器)
8.0 函数式组件使用props
本篇文章名称叫组件实例的三大核心属性,是因为组件当是实例时才有三大属性(相当于才有指向实例本身的this),但是,即使是简单组件也是有props属性的,相当于函数是可以通过传参的形式传递props。
<script type="text/babel">
//创建组件
function Person (props){
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
</script>
总结:
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
- 对props中的属性值进行类型限制和必要性限制,React v15.5 开始已弃用
类名.propTypes = {}
,转为使用prop-types库进限制(需要引入prop-types库)
React v15.5以前:
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number
}
新的方式:
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.
}
9.0 refs与事件处理
refs和事件处理放一起学习。
首先编辑一个demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>hello_react</title>
</head>
<!-- 1.0 准备容器 -->
<div id="test"></div>
<!-- 2.0引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 3.0 引入react-dom,用于支持react操作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>
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
class Demo extends React.Component{
render(){
return(
<div>
<input type='text' placeholder='点击按钮提示数据'/>
<button>点击我提示左侧的数据</button>
<input type='text' placeholder='失去焦点提示数据'/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
</body>
</html>
进一步完善如下:
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
const {input1} = this.refs
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this.refs
alert(input2.value)
}
render(){
return(
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
可以理解为ref取代原生中id在开发过程中的作用。
10.0 回调形式的ref
字符串形式的ref(最老的形式)已经不被react官方推荐,而且在其文档中表示之后的新版本更新很可能把这种形式废弃掉。
进入官网-文档-高级指引-Refs&DOM:
https://react.docschina.org/docs/getting-started.html
如下图所示,这是最新的写法:
再往下滚动:
点击“一些问题”:
说白了,就是写多了效率就不高了,所以react官方并不推荐字符串形式的ref。
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
const {input1} = this
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this
alert(input2.value)
}
render(){
return(
<div>
<input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>
12.0 回调形式的ref中回调执行次数的问题
<script type="text/babel">
//创建组件
class Demo extends React.Component{
state = {isHot:false}
showInfo = ()=>{
const {input1} = this
alert(input1.value)
}
changeWeather = ()=>{
//获取原来的状态
const {isHot} = this.state
//更新状态
this.setState({isHot:!isHot})
}
saveInput = (c)=>{
this.input1 = c;
console.log('@',c);
}
render(){
const {isHot} = this.state
return(
<div>
<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>
<button onClick={this.showInfo}>点我提示输入的数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
运行,点击“点我切换天气”:
虽然这样无关紧要,但可以优化成如下代码:
<script type="text/babel">
//创建组件
class Demo extends React.Component{
state = {isHot:false}
showInfo = ()=>{
const {input1} = this
alert(input1.value)
}
changeWeather = ()=>{
//获取原来的状态
const {isHot} = this.state
//更新状态
this.setState({isHot:!isHot})
}
saveInput = (c)=>{
this.input1 = c;
console.log('@',c);
}
render(){
const {isHot} = this.state
return(
<div>
<h2>今天天气很{isHot ? '炎热':'凉爽'}</h2>
<input ref={this.saveInput} type="text"/><br/><br/>
<button onClick={this.showInfo}>点我提示输入的数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
13.0 createRef(推荐使用)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>4_createRef</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作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 Demo extends React.Component{
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
*/
myRef = React.createRef()
myRef2 = React.createRef()
//展示左侧输入框的数据
showData = ()=>{
alert(this.myRef.current.value);
}
//展示右侧输入框的数据
showData2 = ()=>{
alert(this.myRef2.current.value);
}
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>
</body>
</html>
React.createRef
调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的。
14.0 recat的事件处理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件处理</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作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 Demo extends React.Component{
/*
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
(2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
*/
//创建ref容器
myRef = React.createRef()
myRef2 = React.createRef()
//展示左侧输入框的数据
showData = (event)=>{
console.log(event.target);
alert(this.myRef.current.value);
}
//展示右侧输入框的数据
showData2 = (event)=>{
alert(event.target.value);
}
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>
</body>
</html>
END`