React学习 ---- 基础知识学习
文章目录
- 1. 初识React
- 1.1 组件化
- 1.1
- 1.1.2 事件绑定中的this
- 1.2 JSX语法
- 1.2.1 认识JSX及其基本使用
- 1.2.1.1 认识JSX
- 1.2.1.2 JSX基本书写规范
- 1.2.2 JSX事件绑定
- 1.2.3 条件渲染
- 1.2.4 列表渲染
- 1.3 JSX本质和原理
- 1.4 React脚手架
- 2. React语法
- 2.1 组件化开发
- 2.1.1 类组件
- 2.1.2 函数式组件
- 2.2 生命周期
- 2.3 组件间通信
- 2.3.1 父传子
- 2.3.2 子传父
- 2.4 组件插槽
- 2.5 非父子通信
- 2.5.1 context
- 2.5.2 事件总线
- 2.6 setState使用详解
- 2.6.1 为什么使用setState?
- 2.6.2 setState的详细使用
1. 初识React
1.1 组件化
1.1
1.1.2 事件绑定中的this
在类中定义一个函数,将它绑定在
button
上,那么当前函数的this
指向谁呢?
class App extends React.component {constructor() {this.state = {message: 'hello world'}// 对需要绑定的方法,提前绑定好thisthis.btnClick = this.btnClick.bind(this)}// methodsbtnClick() {this.setState({message: 'hello react'})}// renderrender() {return (<div><h1>{this.state.message}</h1><button onClick={this.btnClick}>修改message</button></div>)}}
- 默认情况下,
this
的指向是undefined
- 在button上绑定的事件实际上是将btnClick代码块直接给了onclick,执行的时候相当于
- btnClick( )
- // 此时也就是this绑定规则中的默认绑定,在非严格模式下应该指向
window
,但是我们使用babel对代码解析,也就是在严格模式下,此时的this
指向undefined
- 那么我们就需要给btnClick绑定this,下面是两种方法:
-
- < button onClick={this.btnClick.bind(this)} > 修改message</ button>
-
- 在constructor中,this.btnClick = this.btnClick.bind(this)
- 更推荐第二种,因为若有多个button,那么每个button都需要处理this的重定向,会造成代码冗余
-
1.2 JSX语法
1.2.1 认识JSX及其基本使用
1.2.1.1 认识JSX
const element = <h1>{message}</h1>
这段代码其实就是一段JSX的语法。
JSX语法:
- JSX是JavaScript的语法扩展,也叫做javascript XML,因为看起来像是XML代码;
- 它用来描述UI界面,而且可以和javascript融合起来使用;
- 它不同于Vue的模版语法,不需要专门学习模版语法中的一些指令(v-bind、v-for、v-model…)
为什么React选择了JSX?
- React认为
渲染逻辑
和UI界面
存在内在耦合- UI组件需要绑定事件
- UI组件需要展示数据状态
- 数据改变后又需要改变UI
他们之间是密不可分的,所以React没有将标记分离到不同的文件中,而是将他们组合在了一起,这个地方就叫做组件。
1.2.1.2 JSX基本书写规范
顶层只能有一个根元素
,所以一般我们使用< div >包一下- 为了方便阅读,通常会包裹一个
( )
,将JSX当做一个整体看待,而且这样也方便换行 - 其中可以有
双标签
,也可以有单标签
(例:< h1 ></ h1>、< App />)
render(){return (<div></div>)
}
- JSX的注释写法
render(){return (<div>{ /* 注释 */ }</div>)
}
- JSX嵌入变量作为子元素
- 变量是
Number
、String
、Array
类型时可以直接显示; - 变量时
null
、undefined
、Boolean
时,内容为空(若需要展示出来需要转化成字符串形式) Object
类型不能作为子元素
- 变量是
- 插入表达式或三元运算符或执行函数
render() {const { message, count, array, a, b, c, obj, movies } = this.statereturn (<div>{message + "+" + count} // 运算表达式{count === 0 ? 'true' : 'false'} // 三元运算符<ul>{movies.map(m => <h1>{m}</h1>)}</ul> // 函数</div>)}
-
JSX
绑定属性title
、class
、id
、src
、内联样式绑定
- 基本属性绑定
render() {const src = "https://www.baidu.com"return (<a href={src}></a>)} }
- class样式绑定
rende() {const isActive = falseconst classNames = ['a', 'b']if (isActive) classNames.push('c')return (<div><h1 className={`a b ${isActive ? 'ab' : 'abc'}`}></h1><h1 className={classNames.join(" ")}></h1></div>)} }
- style样式绑定
- 样式的绑定需要以对象的形式进行 绑定
render() {return (<div><h1 style={{ color: "red", fontSize: "20px" }}>哈哈哈哈哈</h1></div>)} }
1.2.2 JSX事件绑定
this的绑定问题
- 上面我们说了使用
bind()
来改变this
的指向,这里不再多说; - ES6中Class出现了
class fields
,所以我们可以使用箭头函数来改变this
的指向(此时的this将相外层寻找,最终找到组件实例对象,这种方法不常用,了解即可)
class App extends React.Component{...btnClick = ()=>{console.log('btnClick',this) }render() {return (<div><button onClick={this.btnClick}>btnClick</button></div>)}}
}
- 第三种方法是直接传入一个箭头函数(常用!!!)
这里的this
,相当于是显式绑定
,this
向外寻找,最终找到组件实例对象。
class App extends React.Component{...btnClick (){console.log('btnClick',this) }render() {return (<div><button onClick={()=>this.btnClick()}>btnClick</button></div>)}}
}
为什么这么推荐第三种方式呢?
当我们遇到event
参数和其他参数的传递时,前面两种事件的绑定方式并不能很好的传递。
现有两个按钮,第一个按照bind()
传递参数,第二种按照箭头函数
传递。
class App extends React.Component {...btnClick(event, name, age) {console.log('event', event);console.log('name', name);console.log('age', age);console.log('this', this);}render() {const { message } = this.stateconst { name, age } = {name: 'why',age: 18}return (<div><button onClick={this.btnClick.bind(this, name, age)}>按钮1</button><button onClick={(event) => this.btnClick(event, name, age)}>按钮2</button></div>)}}
- 使用
bind( )
传递
- 使用
箭头函数
传递
显然,第一种传递方式出现参数不对应的情况,第二种参数严格对应,所以我们更推荐第二项种传递方法。
1.2.3 条件渲染
条件渲染的常用三种方式
- 根据变量值决定渲染内容
render() {const { isShow } = this.stateconst showEl = null;// 1. 根据变量值返回需要的elementif (isShow) {showEl = <h1>条件渲染1{isShow}</h1>} else {showEl = <h1>条件渲染2{isShow}</h1>}return (<div>{showEl}</div>)}
- 三元运算符
render() {const { isShow } = this.stateconst showEl = null;return (<div><div>{isShow ? <h1>三元运算符1</h1> : <h2>三元运算符2</h2>}</div></div>)}
- &&逻辑与运算
render() {const { isShow } = this.stateconst showEl = null;return (<div><h1>{person && <div>{person.name + "" + person.age}</div>}</h1></div>)}
v-show
的效果我们也能实现。
{/* v-show效果实现 */}
<div style={{ display: isShow ? 'block' : 'none' }}>{person.name}</div>
1.2.4 列表渲染
在react
中,对于列表渲染,我们最常用的就是map高阶函数
,对数据进行处理时我们会使用filter
、slice
等函数。
列表渲染中的key
也很重要,我们需要key
作为唯一标识,主要是为了提高diff算法
的效率。
class App extends React.Component {constructor() {super()this.state = {students: [{ id: 1, name: '蒋敦豪', age: 29 },{ id: 2, name: '鹭卓', age: 28 },{ id: 3, name: '李耕耘', age: 27 },{ id: 4, name: '李昊', age: 26 },{ id: 5, name: '赵一博', age: 25 },{ id: 6, name: '张钥沅', age: 24 },{ id: 7, name: '赵小童', age: 23 },{ id: 8, name: '何浩楠', age: 22 },{ id: 9, name: '陈少熙', age: 22 },{ id: 10, name: '王一珩', age: 20 },]}}render() {const { students } = this.statereturn (<div><h1>学生列表</h1><div>{students.map(item => {return (<div key={item.id}>{'排行:' + item.id + "姓名:" + item.name + " 年龄:" + item.age}</div>)})}</div></div>)}}
1.3 JSX本质和原理
实际上,JSX
语法是React.createElement(component, props, ...children)
的语法糖。
- 所有的JSX都将通过
React.createElement()
被转换。
三个参数:
component
– ReactElement的类型- 可以是普通标签
<div>
; - 也可以是组件标签
<App>
;
- 可以是普通标签
props
– 标签属性class
、title
、自定义属性
等- 都以对象的形式传递
children
– 子标签- 以数组的形式传递
这里我们可以知道babel
解析后有了react
的虚拟dom
之后才是生成浏览器dom节点。
插一嘴:虚拟DOM的作用是什么?
-
更新数据
首先是在更新数据时减少浏览器渲染压力。
当页面中数据改变后会触发render()
,重新渲染虚拟DOM,而diff
算法会将新旧DOM做对比,从而有选择的重新渲染更改的DOM; -
跨端渲染组件/控件
React中的虚拟DOM若在web
端显示,则会渲染成对应的div
、button
等组件;若在ios或者android
显示,那么就会渲染成对应的uiView
、UIButton
控件。很好的做到了跨端开发。 -
命令式变成—>声明式编程
- 你只需要告诉React希望让UI是什么状态;
- React来确保DOM和这些状态是匹配的;
- 你不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来;
现在来做一个图书总价的小demo,
其中我们在增加或减少某类图书的购买数量时是这样做的:
因为React中修改数据不能直接修改,所以需要先做浅拷贝,再进行赋值
crease(index) {const newBooks = [...this.state.books]newBooks[index].count++this.setState({books: newBooks})}
// 减少图书数量也同理
1.4 React脚手架
脚手架都是基于node环境,在使用脚手架前需先搭建node环境;
安装react
脚手架
npm install create-react-app -g
创建react
项目
craete-react-app [项目名称]
了解react初始文件:
2. React语法
2.1 组件化开发
组件化开发
时React的三大特点之一,当然,其他的框架也有使用到组件化思想,但是React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分为很多类组件:
- 根据组件定义
函数组件
和类组件
- 根据组件内部是否有需要维护的状态
无状态组件
和有状态组件
- 一般来说,类组件是
有状态组件
,而函数组件时无状态组件
- 根据不同职责
展示型组件
和容器型组件
2.1.1 类组件
render()
的return 返回值
2.1.2 函数式组件
函数式组件
使用function
进行定义,其返回值与类组件返回值一致。
函数式组件特点:
- 没有生命周期,也会被挂载或更新,但是没有生命周期;
this
关键字不能指向组件实例(因为没有组件实例);- 没有内部状态(state)
2.2 生命周期
2.3 组件间通信
2.3.1 父传子
首先接触到的是父传子,跟vue
的v-bind
类似,我们将参数放在子组件身上,通过props接受。
<MainProjectList title="推荐列表" />
import axios from 'axios'
import { Component } from 'react'export class MainProjectList extends Component {constructor(props) {super(props)this.state = {projectList: []}}render() {const { projectList } = this.statereturn (<div className="main-project-list"><h2>{this.props.title}</h2></div>)}
}export default MainProjectList
在之前使用vue
开发时,我们一般使用TypeScript
进行类型验证;在React中,我们可以使用Flow
或TypeScript
进行类型验证,但最常用的还是proptypes
这个库。
// 这里代码提示可能不太灵敏,需要自己手动修改一下
MainProjectList.propTypes = {title: PropTypes.string.isRequired
}
默认值:
MainProjectList.defaultProps = {title: '推荐列表'
}
2.3.2 子传父
在vue
中子传父是使用自定义事件
, 同样也是通过props
进行传递
父组件:
changeCount(count) {this.setState({count: this.state.count + count})}render() {const { count } = this.statereturn (<div>Footer<h1>总计数:{count}</h1><AddCount changeCount={count => this.changeCount(count)} /></div>)}
子组件:
export class AddCount extends Component {render() {return (<div><button onClick={() => this.props.changeCount(1)}>+1</button><button onClick={() => this.props.changeCount(5)}>+5</button><button onClick={() => this.props.changeCount(10)}>+10</button></div>)}
}AddCount.propTypes = {changeCount: PropTypes.func.isRequired
}
2.4 组件插槽
React
中没有对插槽的明确定义,但是可以通过下面两种方式实现:
children
React
中创建虚拟DOM使用React.createElement([标签元素名称], {标签属性}, ...children)
,直接使用双标签书写组件,即可在双标签之间传入子组件。
父组件:
<NavBar ><button>按钮</button><h2>标题</h2><p>...</p></NavBar>
子组件:
render() {const { children } = this.propsreturn (<div className="nav-bar"><div className="left">{children[0]}</div><div className="center">{children[1]}</div><div className="right">{children[2]}</div></div>)}
但是这种情况有一个弊端:如果传入的children
是一个数组,那么使用数组索引可以正常显示传入的传输;但若是传入单个元素,那么children
将不会是数组的形式,那么使用数组索引进行展示将会报错;同样的,传入数组,使用单标签显示虽然代码不报错,但是不会出现想要的效果。
props
(推荐!!! )
父组件:
const btn = <button>props按钮</button>const title = <h1>props标题</h1>const ellipse = <p>...</p>...<NavBar leftSlot={btn} centerSlot={title} rightSlot={ellipse} />...
子组件接收数据跟父传子一样,这里不再过多赘述。
作用域插槽的实现:
跟Vue类似,都是由子组件决定插槽传入的内容。这里不做赘述。
2.5 非父子通信
组件传参简写方式
const { info } = {name: 'cr',age: 30}
...
<ContextTest name={info.name} age={info.age} />
<ContextTest {...info} />
...
2.5.1 context
context
是React
官方封装的全局传参的一种方式,类似与vue中的provide
和inject
,使用较为麻烦,项目中不常用,更多的是使用Redux
。
- theme-context.js
在单独文件中创建theme-context,方便在多个文件中使用
import React from "react"// 这里传递的值是默认值,之后若使用到<ThemeContext.Provider value={{}}>进行传值,那么默认值将会被改变
const ThemeContext = React.createContext({'color': 'red','font-size': '20px'}
)export default ThemeContext
- 传递:
使用<ThemeContext.Provider>
进行传递,value
是必填项
import ThemeContext from './Components/context/theme-context'
...
<ThemeContext.Provider value={{'color': 'red','font-size': '20px'}}><ContextTest></ContextTest></ThemeContext.Provider><Footer />
- 接收:
...class ... {render(){// 在render中就可以通过this.context使用传递过来的数据了return ()}
}
// 为当前组件绑定指定的context
ContextTest.contextType = ThemeContextexport default ContextTest
注:
在函数式组件中怎么使用
context
?
在函数式组件中直接为当前组件指定context
是不可以的,因为函数式组件没有实例,无法添加contextType
,这时候可以通过<ThemeContext.Consumer>
进行数据的接收。
import ThemeContext from "./context/theme-context";
// 函数式组件function AppFunc() {// 返回值与类组件返回值一致return (<div><ThemeContext.Consumer>{value => {console.log('value', value); return <div>{value.color}</div>}}</ThemeContext.Consumer></div>)
}export default AppFunc
当然了,<ThemeContext.Consumer>
也不止这一种用法,类组件中contextType
只能绑定一个context
,当我们需要下一个组件中同时使用到多个context
时就可以使用<ThemeContext.Consumer>
。
- 一个Provider可以和多个消费组件有对应关系;
- 多个Provider也可以嵌套使用,里层的会覆盖外层的数据;
2.5.2 事件总线
与vue
类似,不在过多赘述,知道在React中也可以使用(并且会使用)即可。
2.6 setState使用详解
2.6.1 为什么使用setState?
如果我们直接通过this.state.xxx="..."
来修改state中的数据,那么页面数据不会更改,这是因为React不能感知到这种修改;
这种修改方式在vue
中能够行得通是因为vue
使用Object.defineProperty()
或者Proxy
做了数据劫持来监听数据变化;
在React
中我们必须通过setState
来告知数据发生变化。
2.6.2 setState的详细使用
class App extends React.Component {constructor(props) {// 传入props 自动保存父组件传递的数据 super(props)this.state = {name: 'App',info: {name: 'cr',age: 30}}}this.setState({name: 'aa'})
}
这里的setState
其实是又创建了一个新对象,之前的this.state
是旧对象,数据的替换是通过Object.abssign(this.state, newState)
将新旧数据进行了合并;
那也就是说我们在使用this.setState()
时只要给这个函数传入一个对象即可,那我们可以直接传入对象,也可以传入一个返回对象的回调函数。
// 第一种
this.setState({name: 'aa'})// 第二种
this.setState(()=>{return {name: 'aa'}})
推荐第二种方式,
- 首先,我们可以在回调函数中对state数据做些处理;
- 其次,在这个回调函数中我们可以直接拿到之前的
state
和props值
,比第一种方式少了this
的使用;
this.setState((state, props)=>{return {name: 'aa'}
})
setState()
在React
的事件处理中是一个异步调用!
如果希望在数据更新之后,获取到对应的结果执行一些逻辑代码,那么就可以在setState()
中传入第三个参数:callback
this.setState({name:'2222'}, ( )=>{console.log(this.state.name) // 2222
})
console.log(this.state.name) // aa
面试题:为什么
setState
设计为异步
?
-
显著提升性能
如果同时有多个setState()
产生,一次setState
就执行一次render
,界面多次渲染,这样会太消耗性能;
所以React
选择获取到多次更新,批量执行setState()
更新 -
如果同步更新了state,但是还没有执行
render
,就会导致state
和props
不同步
setState
一定是异步更新吗?
不是的。
在React18
之前,把setState
放在setTimeOut
或者原生DOM
中,那么它就是同步执行的,因为setState()
被交给了浏览器,不再是React
负责了;
而在React18
之后,默认所有的操作都被放到了批处理中,都变成了异步操作;