React简介
React是用于构建用户界面的JavaScript库,起源于FaceBook的内部项目,于 2013 年 5 月开源。
React主要用于构建UI。你可以在React里传递多种类型的参数,如声明代码,帮助你渲染出UI、也可以是静态的HTML DOM元素、也可以传递动态变量、甚至是可交互的应用组件。
React的特点
- 1.声明式设计 −React采用声明范式,可以轻松描述应用。
- 2.高效 −React通过对DOM的模拟,最大限度地减少DOM操作。
- 3.灵活 −React可以与已知的库或框架很好地配合,可以在无需重构代码的情况下使用React实现新功能。
- 4.JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
- 5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
- 6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
安装
在不使用脚手架的情况下,我们可以下载React相关的包进行使用,也可以使用CDN
这里我使用的是v16.4.0
版本
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>
Hello React
新建一个html文件,编写如下代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello_react</title>
</head>
<body>
<!-- 准备容器 -->
<div id="test"></div>
<!--引入react核心库 核心库必须最先引入-->
<script type="text/javascript" src="../reactjs/react.development.js"></script>
<!--引入react拓展库,react-dom,支持react操作dom-->
<script type="text/javascript" src="../reactjs/react-dom.development.js"></script>
<!--引入babel,用于将jsx转换为js-->
<script type="text/javascript" src="../reactjs/babel.min.js"></script>
<!-- 此处一定要写text/babel,使用babel翻译jsx,默认js-->
<script type="text/babel">
// 创建虚拟dom
const VDOM = <h1>hello,react</h1> // jsx语法
// 渲染虚拟dom
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
</body>
</html>
这里我是下载了React相关js文件到本地进行使用
需要注意的是,用来编写jsx语法的script标签,需要把type设置为babel,否则就不能使用jsx语法。
注:
接下来的所有代码都写在类型为“babel”的script标签中(type=“text/babel”)
在这个例子中,先准备了一个id为test的一个容器,然后使用jsx语法创建了一个虚拟DOM,也就是h1
,最后使用ReactDOM的render方法将虚拟DOM进行渲染。
最后在浏览器打开
看到如下内容,第一个React程序就写完了。
创建虚拟DOM的方式
使用jsx进行创建
jsx
支持在js代码中嵌入标签,可以让编码人员更加简单的创建虚拟DOM
// 创建虚拟dom
const VDOM = <h1 id="title">hello,react</h1> // jsx语法
// 渲染虚拟dom 虚拟DOM, 获取节点进行挂载
ReactDOM.render(VDOM,document.getElementById('test'))
使用js进行创建
使用React.createElement(标签名,标签属性,标签内容)
创建一个虚拟DOM,
// 创建虚拟dom
const VDOM = React.createElement('h1',{id:'title'},'hello react!')
// 渲染虚拟dom
ReactDOM.render(VDOM,document.getElementById('test'))
如果需要创建子节点,则需要将标签内容修改为使用React.createElement
方法创建的虚拟DOM
// 多层标签嵌套
const VDOM = React.createElement('div',{id:'title'},
React.createElement('h1',{id:'title'},'hello react!'))
// 渲染虚拟dom
ReactDOM.render(VDOM,document.getElementById('test'))
其实,jsx
创建的虚拟DOM最终编译后得到代码和js方式创建的是一样的,但是jsx
更加简单,而且当面多多重标签深度嵌套的时候,代码会显得很冗余,所以一般情况下建议使用jsx
。
虚拟DOM与真实DOM的区别
先编辑以下代码,在浏览器打开控制台,查看打印的结果
// 创建虚拟dom
const VDOM = <h1 id="title">hello,react</h1> // jsx语法
// 渲染虚拟dom
ReactDOM.render(VDOM,document.getElementById('test'))
console.log("虚拟dom",VDOM)
//真实dom
const TDOM = document.getElementById('demo')
console.log("真实dom")
console.dir(TDOM)
总结一下
-
虚拟dom本质就是Object类型的js对象(一般对象)
-
真实dom属性多,虚拟dom属性少,比较轻
-
虚拟dom最终会被react转换为真实dom,呈现在页面上
jsx语法规则
jsx 语法规则:
-
1.定义虚拟dom时,不要写引号\
-
2.标签中混入js
表达式
时,使用{}
,注意区分js语句
,表达式
表达式
:一个表达式会产生一个值,- 例:
a
,a+b
,demo(1)
,arr.map()
,function test() {}
- 例:
语句
:- 例:
if(){}
,for(){}
,switch(){case:xxx}
- 例:
-
3.样式类名指定不用
class
,用className
(class 是 ES6里类定义关键字,为了避免冲突) -
4.内联样式要加双花括号
style={{key:value}}
-
5.虚拟dom只能有一个根标签
-
6.标签必须闭合
-
7.标签首字母
-
(1)若小写字母开头,则将标签改为html中同名元素,若html中没有该标签同名元素,则报错
-
(2)若大写字母标签,react就去渲染对应的组件,若组件没有定义,则报错。
-
const myId = "title"
const myData = "hello jsx"
const VDOM = (
<div>
<h2 id={myId} className="title">
<span style={{color: "green",fontSize:'20px'}}>{myId}</span>
</h2>
</div>
)
ReactDOM.render(VDOM,document.getElementById("jsx"))
注:
使用jsx语法写标签时,不能在标签中写注释,只能写在{}包含的语句块中例:{/*这是一段注释*/}
小练习——列表渲染
先定义几个模拟数据,放在一个列表中,然后遍历列表,取出列表中的元素,渲染在页面上
//模拟一些数据
const data = ["AngularJS", "ReactJS", "VueJS"]
// 创建虚拟dom
const VDOM = (
<div>
<h1>前端框架列表</h1>
<ul>
{
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
ReactDOM.render(VDOM,document.getElementById("jsx"))
为什么在{}
中没有写for循环?因为在{}
中只能写表达式,不能写语句,for循环就是一个语句。
使用map取出数组中的元素,返回一个被li
标签包含的元素集合。这里的key
则是该DOM元素的唯一标识,不应该重复,在react渲染DOM时,会将新的虚拟DOM
的key
与旧的虚拟DOM
的key
进行对比,保留已经存在的节点,并添加新的节点。也就是说,如果在上面的列表中再添加一个元素,react只会渲染新添加的这个元素,其他的保持不变,通过复用节点的方式提高效率。如果没有这个key
,则无法判断两个节点是否相同,只能全部重新渲染,如果实在节点较多的情况下,容易使得浏览器卡顿。
最后看一下效果
组件的定义
函数式组件
创建函数式组件 函数名就是类名,例如:
function MyComponent(){
return <h2>函数定义组件(适用于 "简单组件" 定义)</h2>
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/> ,document.getElementById("test"))
函数式组件:
- 函数和组件标签首字母必须大写
- 函数必须有返回值
- render必须写闭合标签,
<MyComponent/>
或<MyComponent></MyComponent>
ReactDom.render 做了什么
-
- react解析组件标签,找到 MyComponent 组件。
-
- 发现组件是函数定义的,随后调用该函数,将返回的虚拟dom转换为真实dom,呈现在页面中。
类式组件
类式组件,通过类来定义一个组件,类名就是组件名,例如:
class MyComponent extends React.Component {
render(){
return <h2>类定义组件(适用于复杂组件的定义)</h2>
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"))
在这个示例中,定义了一个MyComponent
类继承React.Component
(这个继承是创建类式组件所必须的)
在类式组件中,必须写render方法并返回虚拟DOM。
注:
自定义组件首字母大写(类名大写)
render是放在哪里的? —— 类的原型对象上,供实例使用。 MyComponent.prototype
render中的this指向哪里 —— MyComponent的(组件)实例对象。
ReactDom.render 做了什么
-
- react解析组件标签,找到 MyComponent 组件。
-
- 发现组件是使用类定义的,随后new出来该类的实例,
-
- 并通过该实例调用原型上的render方法,将render返回的虚拟dom转换为真实dom,呈现在页面中
复合组件
定义一个组件作为父组件,在父组件中使用子组件,示例:
class PersonComponent extends React.Component {
render() {
return (
<div>
<div>父组件</div>
<SonComponent />
</div>
)
}
}
class SonComponent extends React.Component {
render() {
return (
<div>子组件</div>
)
}
}
React三大属性
接下来的内容基本都是在针对类式组件进行展开,座椅学习接下来的内容之前,需要对类的相关知识有一定的了解
state
在前面的例子中,我们一直在使用ReactDOM.render()
来渲染我们定义的组件,但是第一次挂载完成后,我们很难再修改组件的样式或状态。
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
React 里,只需更新组件的 state,React则会根据新的 state 重新渲染用户界面(不要操作 DOM),也就是状态驱动着组件的更新。
创建一个类定义的组件(为何是类定义?因为在旧版本的React中,函数定义组件不支持state和refs)
// 创建类定义组件
class Weather extends React.Component {
// 构造器调用几次? --- 1次
constructor(props) {
super(props);
// console.log(this) //这里的this是组件实例
// state必须是一个对象
this.state = {isHot: false,wind:'微风'}
// 解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
// 注意:通过类调用方法和通过实例调用不同
changeWeather() {
// changeWeather是作为onClick的回调,所以不是通过实例调用,是直接调用
// 类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefind
// 获取isHot原始值
const isHot = this.state.isHot
// 原始值取反,使用setState进行更新,(合并,非替换)
this.setState({isHot: !isHot})
}
render(){
// 读取状态
const { isHot, wind } = this.state
return (
{/*这里是通过类直接调用方法,this指向不为实例,为undefind*/}
<h1 onClick={ this.changeWeather }>
今天天气{ isHot ? "炎热" : "凉爽" },{wind}
</h1>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById("test"))
这个示例实现的功能是,通过点击组件,修改组件的state
,实现组件内容的改变
注!!:
react不认可直接修改state借助内置api去更改,通过setState方法进行修改,该方法接受一个对象,在对象中添加需要修改的属性值。
首先在构造器中初始化state
,添加相关的状态,例如isHot
、wind
,在render中使用了一个三元表达式,根据isHot的真假显示”炎热“或者”凉爽“。
给h1
标签添加了一个点击事件,定义了changeWeather
方法作为点击事件的回调。
onClick={ this.changeWeather }
语句标识将类方法changeWeather
作为点击事件的回调,this指向的是Weather这个类,不能使用this.changeWeather()
,加()表示直接调用该方法,会使得点击事件的回调变成changeWeather
的返回值,但是该方法没有返回值,也就是undefind,使得点击事件不生效。
关于changeWeather
中的this指向问题,在这里需要先了解类方法和实例方法的区别,类方法则是直接通过该类进行调用,例如ClassName.function()
,就是类名.方法名
。实例方法需要先通过new关键字创建一个实例(类的实例)。通过实例名.实例方法
的方式进行调用,两种方法中this的作用域是不同的。
在点击事件中的this.changeWeather
,this指向当前类,是直接调用类方法,类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefind(如果不开启严格模式,则指向window)所以使用this.state
和this.setState
时,会提示undefind。所以我们需要修改this的指向,可以使用bind方法,bind方法会返回一个新的函数,并将修改该函数的作用域。
在构造器中写入this.changeWeather = this.changeWeather.bind(this)
,构造器中的this指向的是当前实例,通过传入当前实例,调用bind方法返回一个新的changeWeather
并挂载在实例上,该方法的this指向当前实例,然后我们就能在changeWeather
这个方法中修改state。
可以在构造器中打印一下this,结合上面内容进行分析
简写一下
可以弃用构造函数,直接将state添加到类属性中
然后类方法使用箭头函数:
- 箭头函数没有自己的this。
- 如果在箭头函数中使用this,则使用外层函数的this作为自己的this。
class Weather extends React.Component {
// 初始化状态
state = {isHot: false,wind:'微风'}
// 自定义方法 赋值语句 + 箭头函数
changeWeather = () => {
const isHot = this.state.isHot
this.setState({isHot: !isHot})
}
render(){
// 读取状态
const { isHot, wind } = this.state
return (
<h1 onClick={ this.changeWeather}>
今天天气{ isHot ? "炎热" : "凉爽" },{wind}
</h1>
)
}
}
props
props用于向组件传递数据
state 和 props 主要的区别在于 props是只读,不可变,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。
props传值
// props传值
class Person extends React.Component {
render() {
const {name, age, sex} = this.props;
console.log(this)
return (
<ol>
<li>{name}</li>
<li>{parseInt(age) + 1}</li>
<li>{sex}</li>
</ol>
)
}
}
// ReactDOM.render(<Person name="四个火" age="21" sex="男"/>, document.getElementById("test"));
const person = {name:"四个火",age:"21",sex:"男"}
ReactDOM.render(<Person {...person}/>, document.getElementById("test"));
可以直接在标签内写自定义属性,写在组件标签内的属性会被添加到实例的props属性中,也可以使用展开运算符{...person}
这里的 展开语法与ES6有些不同,ES6中的展开运算符不允许展开对象,React组件标签内允许,其他地方不允许
打印一下当前组件实例
可以看到我们添加的属性都被添加到props中了
然后就可以在组件中使用props里面的数据
props限制
有时候我们希望对props的数据进行限制,例如是否必传,默认值、数据类型等
可以使用React.PropTypes
完成该需求,在15.5版本后的React中,为了减轻React的重量,所以该模块被移到了 prop-types
库,用户根据需要自行引入该模块
下载prop-types.js
文件,或CDN引入文件。
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
引入之后,全局多了一个对象 PropTypes,假如现在创建了一个名为Person的组件,为组件添加一个propTypes属性,用于属性限制
Person.propTypes = {
// 类型和必要性限制
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string,
spack: PropTypes.func
}
Person.defaultProps = {
// 默认值限制,如果没有进行传值,则使用默认值
age: 21,
sex: "男"
}
需要注意的是这里的propTypes
首字符小写,表示用于类型限制的属性,PropTypes
首字母大写,是一个对象,用来限制的属性
几种常见限制:
- isRequired 限制必填
- string 限制字符串
- number 限制数值
- object 对象
- array 数组
- func 限制方法 ------ function 由于function是js里面的关键字,所以使用func避免冲突
然后向组件传参数
const person = {name:"四个火",age:21}
ReactDOM.render(<Person {...person} spack={spack}/>, document.getElementById("test"));
function spack() {
alert("spack")
}
虽然没有向组件传递sex,但是由于写了默认参数,所以组件就会默认sex=“男”。
props简写
直接给类添加propTypes
和defaultProps
属性,注意:是类属性,不是实例属性,所以加上static关键字
class Person extends React.Component {
constructor(props) {
/*
* 拓展
* 构造器是否接收props,并传递给super,取决于:是否希望在构造器中使用this访问props
* */
super(props);
console.log(this.props) // 通过实例使用props ,这里如果不传递props给super,this.props为undefind,可直接使用props,如下
console.log(props) //直接使用props
}
static propTypes = { //这里是类属性,不是实例属性,所以加上static关键字,需要注意区分
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string,
spack: PropTypes.func
}
static defaultProps = {
age: 21,
sex: "男"
}
render() {
const {name, age, sex} = this.props;
console.log(this)
return (
<ul>
<li>{name}</li>
<li>{age + 1 /*props是只读的,这个地方不是修改age,只是对展示的值进行计算*/ }</li>
<li>{sex}</li>
</ul>
)
}
}
const person = {name:"四个火",age:21}
ReactDOM.render(<Person {...person} spack={spack}/>, document.getElementById("test"));
function spack() {
alert("spack")
}
这里的构造器一般可以不用添加,只是作为一个拓展,如果希望在构造器中使用this访问props时,需要将props传递给super()。
函数式组件使用props
前面提到,函数式组件不支持state和refs,但是也能使用props以及限制props限制
参考下面的示例
function Person(props) {
const {name,age,sex} = props
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{sex}</li>
</ul>
)
}
Person.propTypes = { //组件属性,不是方法
/**
* 类型限制,必要性限制
*/
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string,
}
Person.defaultProps = {
// 默认值限制,如果没有进行传值,则使用默认值
age: 21,
sex: "男"
}
const person = {name:"四个火"}
ReactDOM.render(<Person {...person} />, document.getElementById("test"));
注意:props限制不能写到方法体里面
refs
在原生js中,通常使用document.getElementById
、document.querySelector
等这种类似的操作来获取我们需要的DOM元素
React为我们提供了一种非常特殊的属性 ref
,你可以用来绑定到 render() 输出的任何组件上
下面是几个适合使用 refs 的情况:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
不能在函数组件上使用 ref
属性,因为他们没有实例
字符串形式的ref
给组件添加ref属性 ref="refName"
,然后就能通过在示例方法中通过this.refs.refName获得DOM。
class Demo extends React.Component {
// 点击按钮时,显示左边输入框的数据
showInputData1 = () =>{
// 通过获取id的方式
// const input = document.getElementById('input1')
// alert(input.value)
// 通过ref
const { input1 } = this.refs
alert(input1.value)
}
// 右边输入框失去焦点时,显示右边输入框的数据
showInputData2 = () => {
const { input2 } = this.refs
alert(input2.value)
}
render() {
return (
<div>
<input ref="input1" id="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showInputData1}>点击提示左边的数据</button>
<input onBlur={this.showInputData2} ref="input2" type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
字符串形式的ref虽然方便,但是效率并不高,所以官方不建议使用,并可能会在未来的版本被移除
回调函数形式的ref
React 还支持另一种设置 refs 的方式,称为“回调 refs”。它能助你更精细地控制何时 refs 被设置和解除。
内联函数语法:ref={ currentNode => this.input1 = currentNode } —— 把ref当前所处的节点挂载在实例自身上,并取名为input1
回调函数语法:ref={ this.input2 } —— 把当前节点传递给回调函数input2
下面是一个完整的示例
class Demo extends React.Component {
// 点击按钮时,显示左边输入框的数据
showInputData1 = () =>{
// 通过ref
const { input1 } = this
alert(input1.value)
}
// 右边输入框失去焦点时,显示右边输入框的数据
showInputData2 = () => {
alert(this.input2.value)
}
saveInput = (currentNode) => {
this.input2 = currentNode
}
/*
* ref={ currentNode => this.input1 = currentNode }
* 把ref当前所处的节点挂载在实例自身上,并取名为input1
* */
render() {
return (
<div>
<input ref={ currentNode => this.input1 = currentNode }/*内联函数*/ id="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showInputData1}>点击提示左边的数据</button>
<input onBlur={this.showInputData2} ref={ this.saveInput /*使用类绑定的函数作为回调函数*/} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
关于refs回调中,回调函数是内联函数与回调函数是类绑定函数的区别:
- 内联函数: 当组件更新时,会触发两次回调,第一次为null,第二次为当前节点,因为react会清空旧的ref并设置新的
- 类绑定函数: 当组件更新时,react会比较两次函数名相同,只会触发一次回调
createRef
使用 React.createRef()
创建ref,并通过 ref
属性附加到 React 元素
React.createRef调用后会返回一个容器,该容器可以存储被ref所标识的节点
render执行的时候,发现有一个ref,而且是用createRef创建的容器,就会将当前ref所在的节点存储到这个容器里面
class Demo extends React.Component {
myRef = React.createRef(); // 一个节点单独使用一个
showInputData = () =>{
const { current } = this.myRef;
alert(current.value)
}
render() {
return (
<div>
<input ref={ this.myRef } id="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showInputData}>点击提示左边的数据</button>
</div>
)
}
}
关于refs回调中,回调函数是内联函数与回调函数是类绑定函数的区别:
- 内联函数: 当组件更新时,会触发两次回调,第一次为null,第二次为当前节点,因为react会清空旧的ref并设置新的
- 类绑定函数: 当组件更新时,react会比较两次函数名相同,只会触发一次回调
React中的事件处理
-
通过onXxx属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件,而不是原生的DOM事件 — 为了更好的兼容性
- React中的事件是通过事件委托方式处理的(通过冒泡的形式委托给最外层的元素) — 为了高效
-
通过event.target得到发生事件的DOM元素对象 ,如下例,不要过渡使用ref
class Demo extends React.Component {
render() {
return <button onClick={ this.logText }>按钮</button>
}
logText= (event) => {
console.log(event.target)
}
}
ReactDOM.render(<Demo />, document.getElementById("test"))
收集表单数据
在 HTML
中,表单元素如 <input>
,<select>
等 表单元素通常保持自己的状态,并根据用户输入进行更新
而在 React
中,可变状态一般保存在组件的 state(状态)
属性中,并且只能通过setState
方法更新。
我们可以通过使 React
的 state
成为 “单一数据源原则” 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为。
这种形式,其值由 React
控制的输入表单元素称为“受控组件”。
那么相反的,值并不由 React
进行控制,该组件自己输入,减少等等,该元素成为”非受控组件“。
非受控组件
现用现取,非受控组件
如下例,只有在触发submit提交的时候,触发回调函数,才会从输入框中取值。
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {usernameInput,passwordInput} = this
alert(`你输入的用户名是${usernameInput.value},密码是${passwordInput.value}`)
}
render(){
return (
<form action="http://www.zhaojiuyi.top" onSubmit={ this.handleSubmit }>
用户名:<input ref={ currentNode => this.usernameInput = currentNode } type="text" name="username"/>
密码:<input ref={ currentNode => this.passwordInput = currentNode } type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
受控组件
在下面这个实例中,通过绑定输入框的change事件,获取value,将value添加到组件状态(state)中,
然后将state添加到输入框的value,实现数据的双向绑定
class Login extends React.Component {
state = {
username: '19812345678', // 添加一个默认值查看效果
password: ''
}
// input框change时的回调,将value添加到state中
usernameInput = (event) => {
const { value } = event.target
this.setState({username: value})
}
passwordInput = (event) => {
const { value } = event.target
this.setState({password: value})
}
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
alert(`用户名:${this.state.username},密码:${this.state.password}`)
}
render(){
return (
<form action="http://www.zhaojiuyi.top" onSubmit={ this.handleSubmit }>
用户名:<input onChange={ this.usernameInput } value={this.state.username} type="text" name="username"/>
密码:<input onChange={ this.passwordInput } value={this.state.password} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
高阶函数与函数柯里化
高阶函数:如果一个函数满足以下2个规范中的任何一个,那么该函数就是高阶函数
- 若A函数,接受的参数是一个函数,那么A函数就可以称为高阶函数
- 若A函数,调用A函数得到的返回值依然是一个函数,那么A就可以成为高阶函数
- 常见的高阶函数:Promise,setTimeOut,forEach.map等
函数的柯里化: 通过函数调用继续返回函数的方式,实现多次接收参最后统一处理的函数编码形式。
class Login extends React.Component {
state = {
username: '',
password: ''
}
saveFromData = (dataType) => {
return (event) => {
this.setState({[dataType]:event.target.value})
}
}
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
alert(`用户名:${this.state.username},密码:${this.state.password}`)
}
render(){
return (
<form action="http://www.zhaojiuyi.top" onSubmit={ this.handleSubmit }>
用户名:<input onChange={ this.saveFromData("username") } value={this.state.username} type="text" name="username"/>
密码:<input onChange={ this.saveFromData("password") } value={this.state.password} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
值得注意的是,此例子使用了saveFromData
传入一个数据类型参数,然后返回一个箭头函数作为onChange
的回调函数,并接收event
参数
也就是说,onChange
的回调不再是saveFromData
函数,而是它返回的一个箭头函数
我们称saveFromData为高阶函数
而在调用saveFromData
方法的同时,我们传入了一个参数,然后使用返回的函数接收了另一个参数,并在返回的函数中使用了统一处理了所有参数,这就是柯里化,看下面这个示例
addNumber = (a) => {
return (b) => {
return b + a
}
}
组件的生命周期
所谓的生命周期,就是指组件从被创建出来,到被使用,更新,最后被销毁的这么一个过程
在这个过程中,React提供了我们会自动执行的不同的钩子函数
,我们称之为生命周期函数,或生命周期钩子
先通过一个简单的案例认识一下生命周期
class Life extends React.Component {
state = {
opacity:1
}
death = () => {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById("test"))
}
// 生命周期回调函数 <==>生命周期钩子:组件挂载完成
componentDidMount() {
// 每隔200毫秒将透明度减少0.1
this.interval = setInterval(()=>{
let {opacity} = this.state
opacity -= 0.1
if (opacity <= 0){
opacity = 1
}
this.setState({opacity})
},100)
}
// 生命周期回调函数:组件即将卸载
componentWillUnmount() {
//清空定时器
clearInterval(this.interval)
}
render() {
return (
<div>
{/*绑定state状态,修改样式*/}
<h2 style={{opacity: this.state.opacity}}>React好难啊,不想学了</h2>
<button onClick={this.death}>不学了</button>
</div>
)
}
}
ReactDOM.render(<Life/>,document.getElementById("test")) //挂载组件
在示例中,定义了一个state,保存组件的透明度状态,我们需要创建一个计时器,每隔一定时间,修改state,将透明度降低
但是我们不能在render中写入我们的定时器,应为定时器会不断更新state,然而state的修改会驱动render方法的调用,这样我们就会不停创建新的计时器,对cpu造成很大的负担
这时候我们希望只在组件挂载时创建一个定时器,这时候就会用到我们的生命周期钩子
componentDidMount
表示组件挂载完成时,也就是说组件挂载完成后会调用这个函数
componentWillUnmount
表示组件即将销毁,也就是说组件销毁之前会触发这个函数
用一张图片表示组件的生命周期
用一个示例来打印表示组件的生命周期,可以打开控制台查看
class Count extends React.Component {
//构造器
constructor(props) {
super(props);
this.state = {num: 0}
console.log("Count---constructor")
}
//组件即将挂载钩子
componentWillMount() {
console.log("Count---componentWillMount")
}
//组件挂载完毕的钩子
componentDidMount() {
console.log("Count---componentDidMount")
}
//即将卸载组件钩子
componentWillUnmount() {
console.log("Count---componentWillUnmount")
}
/**
* 控制组件是否更新的钩子(阀门),返回true则允许更新,否则不允许
* 如果不写该钩子,则默认为true
*/
shouldComponentUpdate() {
console.log("Count---shouldComponentUpdate")
return true
}
// 组件将要更新的钩子,接下来调用render
componentWillUpdate() {
console.log("Count---componentWillUpdate")
}
//组件更新完毕的钩子
componentDidUpdate() {
console.log("Count---componentDidUpdate")
}
render() {
console.log("Count---render")
return (
<div>
<h3>{this.state.num}</h3>
<button onClick={this.addCount}>点击+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>强制更新组件</button>
</div>
)
}
addCount = () => {
let {num} = this.state
this.setState({num: num + 1})
}
death = () => {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById("test"))
}
force = () => {
//强制更新组件,不需要通过shouldcomponentUpdate钩子
this.forceUpdate()
}
/*
* setState后就是shouldComponentUpdate钩子,在到达componentWillUpdate钩子
* forceUpdate跳过了shouldComponentUpdate,直接到达componentWillUpdate
* */
}
// 父组件
class A extends React.Component {
state = {name: "鄙人四个火"}
changeProps = () => {
this.setState({name:"太阳当空丶赵"})
}
render() {
return (
<div>
<div>组件A</div>
<button onClick={this.changeProps}>组件传参</button>
<B name={this.state.name}></B>
</div>
)
}
}
//子组件
class B extends React.Component {
componentWillReceiveProps(){
// 当父组件render时,触发此钩子
console.log("B---componentWillReceiveProps")
}
render() {
console.log("B---render")
return (
<div>
<div>组件B</div>
<div>name:{this.props.name}</div>
</div>
)
}
}
ReactDOM.render(<Count/>, document.getElementById("test"))
ReactDOM.render(<A/>, document.getElementById("test1"))
打开控制台,点击按钮,查看对应打印的生命周期
总结一下:
组件挂载时,会首先触发constructor
构造器,然后触发componentWillMount
钩子(组件即将挂载,也就是挂载前,此时还未进行挂载),然后执行render
方法返回虚拟dom,将虚拟dom转换为真实dom后完成挂载,进入componentDidMount
钩子(完成挂载);
组件更新时,如果是使用setState
方法进行状态更新,则进入shouldComponentUpdate
钩子,判断是否更新状态,就相当于一个开关,如果为false,则不会触发接下来的钩子,用户没有自定义shouldComponentUpdate
钩子时,默认为true,否则更具自定义规则进行,随后进入componentWillUpdate
钩子(组件即将更新,也就是更新之前),再然后调用render
方法,更新组件状态,然后进入componentDidUpdate
钩子(更新完成);如果使用forceUpdate
方法强制更新组件,则会跳过shouldComponentUpdate
钩子(不判断是否更新),直接进入componentWillUpdate
,然后调用render
,最后componentDidUpdate
(更新完成);
如果是父组件触发render
方法,则子组件将触发componentWillReceiveProps
钩子(即将向子组件传递props,也就是即将向子组件传递参数),然后就和正常更新组件状态的钩子一样,到达shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate
;
销毁组件时,进入componentWillUnmount
钩子(即将销毁组件,销毁组件可以调用ReactDOM.unmountComponentAtNode(这里传入一个dom节点))
,最后完成组件的销毁