1、React简介
1.1 react是什么?
React用于构建用户界面的JS库。是一个将数据渲染为HTML视图的开源JS库。
React 应用程序是由组件组成的。一个组件是 UI(用户界面)的一部分,它拥有自己的逻辑和外观。组件可以小到一个按钮,也可以大到整个页面。
1.2 为什么学?
1.原生JS操作DOM繁琐,效率低
2.使用JS直接操作DOM,浏览器会进行大量的重绘重排
3.原生JS没有组件化编码方案,代码复用低
2、React入门
2.1 React 基础案例
1.先倒入三个包:
先引入react.development.js
,后引入react-dom.development.js
react.development.js
react-dom.development.js
babel.min.js
2.创建一个容器
3.创建虚拟DOM,渲染到容器中
<body>
<!-- 准备好容器 -->
<div id="test">
</div>
</body>
<!-- 引入依赖 ,引入的时候,必须就按照这个步骤-->
<script src="../js/react.development.js" type="text/javascript"></script>
<script src="../js/react-dom.development.js" type="text/javascript"></script>
<script src="../js/babel.min.js" type="text/javascript"></script>
<!--这里使用了babel用来解析jsx语法-->
<script type="text/babel">
// 1.创建虚拟DOM
const VDOM = <h1>Hello</h1> //这个地方使用的是JSX语法,不需要加""
// 2.渲染,如果有多个渲染同一个容器,后面的会将前面的覆盖掉
ReactDOM.render(VDOM,document.getElementById("test"));
</script>
</html>
2.2 JSX基础语法
1.定义虚拟DOM,不能使用“”
2.标签中混入JS表达式的时候使用{}
3.样式的类名指定不要使用class,使用className
4.内敛样式要使用双大括号包裹
5.不能有多个根标签,只能有一个跟标签
6.标签必须闭合
7.如果小写字母开头,就将标签转化为html同名元素,如果html中无该标签对应的元素,就报错;如果是大写字母开头,react就去渲染对应的组件,如果没有就报错
关于JS表达式和JS语句:
1、JS表达式:返回一个值,可以放在任何一个需要值的地方
例:
a a+b demo(a) arr.map() function text(){}
2、JS语句:不会返回一个值
例:
if(){} for(){} while(){} swith(){}
实例如下:
<!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>
.sss{
color: red;
}
</style>
</head>
<body>
<!-- 准备好容器 -->
<div id="test">
</div>
</body>
<!-- 引入依赖 ,引入的时候,必须就按照这个步骤-->
<script src="../js/react.development.js" type="text/javascript"></script>
<script src="../js/react-dom.development.js" type="text/javascript"></script>
<script src="../js/babel.min.js"></script>
<!--这里使用了js来创建虚拟DOM-->
<script type="text/babel">
const MyId = "title";
const MyData = "Cyk";
// 1.创建虚拟DOM
const VDOM = (
<h1 id = {MyId.toLocaleUpperCase()}>
<span className = "sss" style = {{fontSize:'50px'}}>sss</span>
</h1>
)
// 2.渲染,如果有多个渲染同一个容器,后面的会将前面的覆盖掉
ReactDOM.render(VDOM,document.getElementById("test"));
</script>
</html>
2.3 两种创建虚拟DOM的方式
1.使用JSX创建虚拟DOM
const VDOM = (
<h1 id = {MyId.toLocaleUpperCase()}>
<span className = "sss" style = {{fontSize:'50px'}}>sss</span>
</h1>
)
这个在上面的案例中已经演示过了 ,下面看看另外一种创建虚拟DOM的方式
2.使用JS创建虚拟DOM
// 1.创建虚拟DOM[在这使用了js的语法]React.createElement(标签,标签属性,内容)
const VDOM = React.createElement('h1',{id:"title"},"nihao")
使用JS和JSX都可以创建虚拟DOM,但是可以看出JS创建虚拟DOM比较繁琐,尤其是标签如果很多的情况下,所以还是比较推荐使用JSX来创建。
3、组件
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
注意:
1、组件名称必须以大写字母开头。
2、React 会将以小写字母开头的组件视为原生 DOM 标签。例如,< div />
代表 HTML 的 div 标签,而< Weclome />
则代表一个组件,并且需在作用域内使用 Welcome
3、传递的参数,不能在组件中改动
3.1 函数式组件
适用于简单组件(无state)
//1.先创建函数,函数可以有参数,也可以没有,但是必须要有返回值 返回一个虚拟DOM
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
//2.进行渲染
ReactDOM.Render(<Welcom name = "ss" />,document.getElementById("div"));
让我们来回顾一下这个例子中发生了什么:
- 我们调用 ReactDOM.render() 函数,并传入
<Welcome name="Sara" />
作为参数。 - React 调用 Welcome 组件,并将
{name: 'Sara'}
作为 props 传入。 - Welcome 组件将
Hello, Sara
元素作为返回值。 - React DOM 将 DOM 高效地更新为
Hello, Sara
。
注意事项:
函数中的this
指向undefined
,因为babel编译后开启了严格模式,严格模式禁止函数中的this指向window
3.2 Class组件(类式组件)
适用于复杂组件(有state)
//必须继承React.Component
//然后重写Render()方法,该方法一定要有返回值,返回一个虚拟DOM
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
//渲染 【这个跟之前也是一样的】
ReactDOM.Render(<Welcom name = "ss" />,document.getElementById("div"));
执行过程:
1.React解析组件标签,找到相应的组件
2.发现组件是类定义的,随后new出来的类的实例,并通过该实例调用到原型上的render方法
3.将render返回的虚拟DOM转化为真实的DOM,随后呈现在页面中
注意:
render是放在类的原型对象上,供实例使用。
render中的this指向类的实例对象
4、组件实例的三大属性
4.1 state
我们都说React是一个状态机,体现是什么地方呢,就是体现在state上,通过与用户的交互,实现不同的状态,然后去渲染UI,这样就让用户的数据和界面保持一致了。state是组件的私有属性。
在React中,更新组件的state,结果就会重新渲染用户界面(不需要操作DOM),一句话就是说,用户的界面会随着状态的改变而改变。
state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
案例:
1.需求:页面显示【今天天气很炎热】,鼠标点击文字的时候,页面更改为【今天天气很凉爽】
核心代码如下:
标准写法
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
//构造器调用几次? ———— 1次,只写了一个weather标签
constructor(props){ //为了操作state
console.log('constructor');
super(props)
//初始化状态
this.state = {isHot:false,wind:'微风'}
//解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render(){
console.log('render');
//读取状态
const {isHot,wind} = this.state
//复杂方法
// const isHot = this.state.isHot;
// const wind = this.state.wind;
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//、这里onClick是赋值语句,不能调用
//changeWeather调用几次? ———— 点几次调几次
changeWeather(){
//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
console.log('changeWeather');
//获取原来的(isHot值
const isHot = this.state.isHot
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换(wind还在)。
this.setState({isHot:!isHot})
console.log(this);
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
注意事项:
- 在构造器
constructor
中初始化state
,且要用对象形式初始化state
- 在
render
函数中创建虚拟DOM时,直接在标签中绑定事件,且事件写法不同于原生JS,如原生JS中的onclick
事件,在react中要写成onClick
,其他同理。 -
onClick={this.changeWeather}
是将this.changeWeather
函数赋值给onClick,函数后面不能加括号,否则就是将函数返回值赋值 - 事件的回调函数要写在类中,此时它放在类的实例对象的原型链上,供实例使用。在本例中,由于
changeWeather
是作为onClick
的回调,所以不是通过实例调用的,是直接调用,且类中的方法默认开启了局部的严格模式,所以其中this的指向不是实例对象,而是undefined
。render函数也是放在对象的原型链上,但是它是由类的实例对象调用的,所以this指向实例对象 - 自定义的事件回调函数的this可以在构造器中用bind更改指向,生成的新函数直接在类中,所以this指向实例对象
注意:
状态必须通过setState
以对象的形式进行更新,且更新是一种合并,不是替换const {isHot,wind} = this.state
是ES6中的对象解构,获取多个对象属性的方法
简写方法
<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从原型上移到实例对象自身,外层函数的this就是箭头函数的this
changeWeather = ()=>{
const isHot = this.state.isHot
this.setState({isHot:!isHot})
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
注意事项:
- 类里可以直接写赋值语句,相当于追加属性(值写死),所以state可以直接在类里通过赋值的形式初始化,而不是在构造函数中
- 自定义回调函数的指向可以通过将箭头函数赋值的方式,从原型上转移到实例对象自身,箭头函数中this指向外层函数中的this,这里即为实例对象
强烈注意:
1、组件中render
方法中的this
为组件实例对象
2、组件自定义的方法中this
为undefined
,如何解决?
a) 强制绑定this
: 通过函数对象的bind()
(可在constructor
中用bind
,也可在绑定事件函数时用bind,即onClick={this.changeWeather.bind(this))
b) 赋值语句加箭头函数
3、状态数据,不能直接修改或更新(setState
)
4.2 props
props主要用来传递数据,比如组件之间进行传值
基本使用:
<body>
<div id = "div">
</div>
</body>
<script type="text/babel">
class Person extends React.Component{
render(){
return (
<ul>
//接受数据并显示
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.sex}</li>
</ul>
)
}
}
//传递数据
ReactDOM.render(<Person name="tom" age = "41" sex="男"/>,document.getElementById("div"));
</script>
如果传递的数据是一个对象,可以更加简便的使用
<script type="text/babel">
class Person extends React.Component{
render(){
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.sex}</li>
</ul>
)
}
}
const p = {name:"张三",age:"18",sex:"女"}
ReactDOM.render(<Person {...p}/>,document.getElementById("div"));
</script>
...
这个符号恐怕都不陌生,这个是一个扩展运算符,主要用来展开数组,如下面这个例子:
arr = [1,2,3];
arr1 = [4,5,6];
arr2 = [...arr,...arr1]; //arr2 = [1,2,,3,4,5,6]
但是他还有其他的用法:
1.复制一个对象给另一个对象{…对象名}。此时这两个对象并没有什么联系了
const p1 = {name:"张三",age:"18",sex:"女"}
const p2 = {...p1};
p1.name = "sss";
console.log(p2) //{name:"张三",age:"18",sex:"女"}
2.在复制的时候,合并其中的属性
const p1 = {name:"张三",age:"18",sex:"女"}
const p2 = {...p1,name : "111",hua:"ss"};
p1.name = "sss";
console.log(p2) //{name: "111", age: "18", sex: "女",hua:"ss"}
注意!! {...P}
并不能展开一个对象
props传递一个对象,是因为babel+react使得{…p}可以展开对象,但是只有在标签中才能使用
对于props限制
很多时候都想要传递的参数进行相应的限制,比如:限制传递参数的类型,参数的默认值等等
react对此提供了相应的解决方法:
1、propTypes
:类型检查,还可以限制不能为空
2、defaultProps
:默认值
<script type="text/babel">
class Person extends React.Component{
render(){
//props是只读的
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.sex}</li>
</ul>
)
}
//对组件的属性对其进行限制
static propTypes = {
name:PropTypes.string.isRequired, //限定name是string类型,并且必须要传递
sex:PropTypes.string, //限定sex是string类型
speak:PropTypes.func //限定speak是function类型
}
//指定默认的标签属性
static defaultProps = {
sex:"男",
age:18
}
}
//在js中可以使用{...p}来复制一个对象,但是这个地方并不是复制对象,而是babel+react通过展开运算符,展开了一个对象
//但是只能在标签中进行使用
//const p = {name:"张三",age:"18",sex:"女"} {14}就代表的是数值
//ReactDOM.render(<Person {...p}/>,document.getElementById("div"));
ReactDOM.render(<Person name="sss" age = {14} speak="8"/>,document.getElementById("div"));
function speak(){
console.log("这个是一个函数")
}
</script>
</html>
函数式组件的使用:
函数在使用props的时候,是作为参数进行使用的(props)
;
function Person(props){
return (
<ul>
<li>{props.name}</li>
<li>{props.age}</li>
<li>{props.sex}</li>
</ul>
)
}
4.3 refs属性
组件内的标签可以定义ref属性来标识自己。
this.refs
拿到真实DOM
1、字符串形式的ref
class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
console.log(this);
const {input1} = this.refs
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
console.log(this);
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 a="1" b="2"/>,document.getElementById('test'))
不被官方推荐,因为效率不高
2、回调ref
1、内联函数(推荐)
<input ref={currentNode => this.input1 = currentNode } type="text" placeholder="点击按钮提示数据"/>
{/*这里的this是指向实例对象,因为箭头函数没有指向,查找外侧的this指向*/}
const {input1} = this //调用的时候直接从this上面取值
注意:
- 函数中的参数
currentNode
是ref所在的节点 - 此时input1是类的属性,即直接绑定到类里,而不是像字符串ref一样添加到refs对象里
- 回调函数:有定义、没有执行(函数名())、最终执行(别人调用)
2、 类绑定函数
saveInput = (c)=>{
this.input1 = c;
console.log('@',c);
}
<input ref={this.saveInput} type="text"/>
3、 回调ref中回调执行次数
内联函数更新时会执行两次,一次清空,一次执行函数,类绑定函数不会。
交互和更改状态的区别:取决于是否修改render函数中节点的内容
createRef(react最推荐)
<script type="text/babel">
class Demo extends React.Component{
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>
-
React.createRef
调用后可以返回一个容器,该容器可以存储被ref
所标识的节点,该容器是“专人专用”的,有多少个节点表示ref
,就要调用多少次React.createRef
3、事件处理
1、通过onXxx
属性指定事件处理函数(注意大小写)
- a)React使用的是自定义(合成)事件, 而不是使用的原生DOM事件——为了更好的兼容性
- b)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)———为了高效
2、通过event.target
得到发生事件的DOM元素对象。发生事件的元素就是操作的元素则可以省略ref。
3、不要过度使用ref
。
4.4 受控组件和非受控组件
以表单提交案例为例
4.4.1 非受控组件
用ref
实现
页面中所有的输入类DOM现用现取,即通过ref标识DOM,进而获取数据
<script type="text/babel">
//创建组件
class Login extends React.Component{
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.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"/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
知识点:
- 表单
<form>
中都用onSubmit
属性来控制提交之后的状态 - 输入DOM(如
<input>
)得有name
属性才能通过GET请求获取到query参数(用?携带) - 删掉
action
无法阻止表单页面刷新以及地址栏更新,得要禁止默认事件event.preventDefault()
-
<button>
的默认type属性值就是submit
4.4.2 受控组件
- 用
onChange
+state
实现 - 页面中所有的输入类DOM将数据存在state中
- 更推荐用受控组件,减少ref的使用
<script type="text/babel">
//创建组件
class Login extends React.Component{
//初始化状态
state = {
username:'', //用户名
password:'' //密码
}
//保存用户名到状态中
saveUsername = (event)=>{
this.setState({username:event.target.value})
}
//保存密码到状态中
savePassword = (event)=>{
this.setState({password:event.target.value})
}
//表单提交的回调
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
4.5 高阶函数与函数柯里化
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
- 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise
、setTimeout
、arr.map()
等等
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
用函数的柯里化实现受控组件
为了不重复编写相似的代码,如saveUsername
和savePassword
<script type="text/babel">
//创建组件
class Login extends React.Component{
//初始化状态
state = {
username:'', //用户名
password:'' //密码
}
//保存表单数据到状态中(函数的柯里化)
saveFormData = (dataType)=>{
return (event)=>{
this.setState({[dataType]:event.target.value})
}
}
//表单提交的回调
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<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('test'))
</script>
注意:
-
this.saveFormData('username')
,有了小括号,立即调用,但是返回的还是一个函数,符合回调函数的要求 -
[dataType]
:调用变量形式的对象属性名 - event形参不需要实参,可以直接调用,所以event不能写进
this.saveFormData('username')
的参数中,得用柯里化形式来体现
不用函数柯里化的实现方式
只需改两处:文章来源:https://www.toymoban.com/news/detail-482644.html
saveFormData = (dataType,event)=>{
this.setState({[dataType]:event.target.value})
}
用户名:<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/>
两种方法都常用文章来源地址https://www.toymoban.com/news/detail-482644.html
到了这里,关于React学习笔记(上)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!