React学习笔记(上)

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"));

让我们来回顾一下这个例子中发生了什么:

  1. 我们调用 ReactDOM.render() 函数,并传入 <Welcome name="Sara" /> 作为参数。
  2. React 调用 Welcome 组件,并将 {name: 'Sara'} 作为 props 传入。
  3. Welcome 组件将 Hello, Sara 元素作为返回值。
  4. 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、组件自定义的方法中thisundefined,如何解决?

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="点击按钮提示数据"/>&nbsp;
						<button onClick={
    
    this.showData}>点我提示左侧的数据</button>&nbsp;
						<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="点击按钮提示数据"/>&nbsp; 
{
    
    /*这里的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="点击按钮提示数据"/>&nbsp;
						<button onClick={
    
    this.showData}>点我提示左侧的数据</button>&nbsp;
						<input onBlur={
    
    this.showData2} ref={
    
    this.myRef2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
					</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个规范中的任何一个,那该函数就是高阶函数。

  1. 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
  2. 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

常见的高阶函数有:PromisesetTimeoutarr.map()等等

函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

function sum(a){
    
    
	return(b)=>{
    
    
		return (c)=>{
    
    
			return a+b+c
		}
	}
}

用函数的柯里化实现受控组件

为了不重复编写相似的代码,如saveUsernamesavePassword

<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>

注意:

  1. this.saveFormData('username'),有了小括号,立即调用,但是返回的还是一个函数,符合回调函数的要求
  2. [dataType]:调用变量形式的对象属性名
  3. event形参不需要实参,可以直接调用,所以event不能写进this.saveFormData('username')的参数中,得用柯里化形式来体现

不用函数柯里化的实现方式
只需改两处:

saveFormData = (dataType,event)=>{
    
    
				this.setState({
    
    [dataType]:event.target.value})
			}
用户名:<input onChange={
    
    event => this.saveFormData('username',event) } type="text" name="username"/>

两种方法都常用

猜你喜欢

转载自blog.csdn.net/weixin_55608297/article/details/131088348