React
React是一个用于动态构建用户界面的JavaScript库
简单使用
- react.js:React 核心库。
- react-dom.js:提供操作 DOM 的 react 扩展库。
- babel.min.js:解析 JSX 语法代码转为 JS 代码的库
在页面中引入库文件,development版方便开发模式下看调试信息
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
</head>
<body>
<!-- 挂载根节点 -->
<div id="test"></div>
<script type="text/babel">
const VDOM = <h1 id="title">hello world</h1>
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
</html>
除了上面这种写法,也可以通过createElement方法创建jsx,相当于babel编译后的结果
<script type="text/javascript">
const VDOM = React.createElement('h1', {id: 'title'}, 'hello world')
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
虚拟DOM和真实DOM的区别:虚拟DOM的本质是一个Object类型的对象,虚拟DOM相比较真实DOM来说,因为没有真实DOM上那么多属性,显得较轻量。虚拟DOM是React内部在用,最终会被React转化为真实DOM,呈现在页面上
JSX语法
JSX用于定义虚拟DOM,在语法层面有以下几点需要注意:
1.定义虚拟DOM时,不要写引号。
2.标签中混入JS表达式时要用{}
。
3.样式的类名指定不要用class,要用className。
4.内联样式,要用style={{key:value}}
的形式去写,key用驼峰形式。
5.只有一个根标签
6.标签必须闭合
7.标签首字母
- .若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
- .若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
注意区分JS表达式和JS语句:
JS表达式会产生一个值,可以放在任何一个需要值的地方,比如:
(1). a
(2). a+b
(3). demo(1)
(4). arr.map()
(5). function test () {}
JS语句是纯粹的逻辑代码,比如:
(1).if(){}
(2).for(){}
(3).switch(){case:xxxx}
定义组件
组件是包含结构(html)、样式(css)和功能(js)的模块。
函数式组件
function MyComponent(){
//此处的this是undefined,因为babel编译后开启了严格模式
console.log(this);
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
render流程:
- React解析组件标签,找到了MyComponent,发现是一个自定义组件。
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
类式组件
//1.创建类式组件
class MyComponent extends React.Component {
render(){
// this是MyComponent的实例对象。
console.log('render中的this:',this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
render流程:
- React解析组件标签,找到了MyComponent组件。
- 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
- 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
组件实例的三大属性
state
state对象数据的状态
class Weather extends React.Component{
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('render');
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//changeWeather点几次调几次
changeWeather(){
// 类中的方法默认开启了局部的严格模式,所以changeWeather中的this要么是undefined要么是组件实例;由于changeWeather是作为onClick的回调,所以不是通过实例调用的,这就导致了this不会指向实例
console.log('changeWeather:', this);
const isHot = this.state.isHot
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot:!isHot})
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
简写形式:
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})
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
props
props用于向组件传递数据
//创建组件
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>
)
}
}
const p = {name:'老刘',age:18,sex:'女'}
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test'))
// 两种写法是等价的,...是JSX的语法糖,不是对象解构
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
使用prop-types库,可以对props数据进行限制
class Person extends React.Component{
render(){
const {name,age,sex} = this.props
//此行代码会报错,因为props是只读的
//this.props.name = 'jack'
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
}
function speak(){
console.log('我说话了');
}
const p = {name:'老刘',age:18,sex:'女', speak}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
因为propTypes和defaultProps是静态属性,因此可以简写成下面这样:
class Person extends React.Component{
//对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
}
函数组件props
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('test'))
refs
refs用于获取DOM节点
方式一:字符串ref
class Demo extends React.Component{
showData = ()=>{
// input1是DOM节点实例
const {input1} = this.refs
alert(input1.value)
}
render(){
return(
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo />,document.getElementById('test'))
方式二:回调函数ref
回调函数ref在render执行时会调用,完成绑定操作。
class Demo extends React.Component{
showData = ()=>{
const {input1} = this
alert(input1.value)
}
render(){
return(
<div>
<input ref={r => this.input1 = r} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
</div>
)
}
}
方式三:createRef函数
React.createRef调用后可以返回一个容器,用于存储被ref所标识的节点。
class Demo extends React.Component{
input1 = React.createRef()
showData = (event)=>{
// console.log(event.target);
alert(this.input1.current.value)
}
render(){
return(
<div>
<input ref={this.input1} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
</div>
)
}
}
表单
受控组件
对表单类控件进行行为控制
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'))
非受控组件
不对表单类控件进行行为控制,组件默认就是非受控组件
//创建组件
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'))
高阶函数
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
函数的柯里化
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
const ret = a(1)(2)(3)
受控组件优化
通过函数颗粒化优化受控组件
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'))
key的作用
key是虚拟DOM对象的唯一标识,当数据状态变更时,用于进行新旧虚拟DOM对象的diffing计算。render只对diffing出的虚拟DOM进行更新。
当状态发生变化后,虚拟DOM进行diffing的规则:
- 若虚拟DOM中内容没变, 直接使用之前的真实DOM
- 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 若虚拟DOM中未找到与新虚拟DOM相同的key,根据数据创建新的真实DOM,随后渲染到到页面
在进行遍历渲染时,通常不建议用index作为key值,因为在进行逆序添加、逆序删除等破坏顺序操作时,且组件用到了表单控件(表单控件值的变化不会被记录),会导致渲染错位,因为key值相同的组件直接复用原来的。
class Person extends React.Component{
state = {
persons:[
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
]
}
add = ()=>{
const {persons} = this.state
const p = {id:persons.length+1,name:'小王',age:20}
this.setState({persons:[p,...persons]})
}
render(){
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加一个小王</button>
<h3>使用index(索引值)作为key</h3>
<ul>
{
this.state.persons.map((personObj,index)=>{
return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Person/>,document.getElementById('test'))
组件生命周期
生命周期表示组件从开始到卸载的整个过程中的一些重要节点。
旧版
初始阶段
由ReactDOM.render()触发---初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount()
componentDidMount,一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
更新阶段
由组件内部this.setSate()或父组件ReactDOM.render()触发
-
shouldComponentUpdate()
-
componentWillUpdate()
-
render()
-
componentDidUpdate()
卸载阶段
componentWillUnmount() ,由ReactDOM.unmountComponentAtNode()触发,一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息。
示例1
class Count extends React.Component{
//构造器
constructor(props){
console.log('constructor');
super(props)
//初始化状态
this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//组件将要挂载的钩子
componentWillMount(){
console.log('componentWillMount');
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('componentDidUpdate');
}
render(){
console.log('render');
const {count} = this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
示例2
//父组件A
class A extends React.Component{
//初始化状态
state = {carName:'奔驰'}
changeCar = ()=>{
this.setState({carName:'奥拓'})
}
render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
//子组件B
class B extends React.Component{
//组件将要接收新的props的钩子
componentWillReceiveProps(props){
console.log('B---componentWillReceiveProps',props);
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('B---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('B---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('B---componentDidUpdate');
}
render(){
console.log('B---render');
return(
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
新版
初始化阶段
由ReactDOM.render()触发---初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount()
更新阶段
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate
- componentDidUpdate()
卸载阶段
componentWillUnmount(),由ReactDOM.unmountComponentAtNode()触发
示例
import React from 'react'
import ReactDOM from 'react-dom'
class Count extends React.Component{
state = {count:0}
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
// 初始挂载执行一次,props/state改变n次,执行1+n次
// 强制更新也会执行
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null
}
// 获取上次的更新快照,数据被componentDidUpdate钩子的最后一个参数接受
// 初始时没有上次的快照,只要有快照就会执行。强制更新也会执行
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return 'hello'
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
//组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue){
console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
}
render(){
console.log('Count---render');
const {count} = this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
class Demo extends React.Component {
state = {
count: 0,
}
change = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={ this.change}>click</button>
<Count count={this.state.count}/>
</div>
)
}
}
export default Demo
getSnapshotBeforeUpdate示例
保存列表停留在当前位置,不随着内容新增自动滚动到最顶部
import React from 'react'
class NewsList extends React.Component{
state = {newsArr:[]}
componentDidMount(){
setInterval(() => {
//获取原状态
const {newsArr} = this.state
//模拟一条新闻
const news = '新闻'+ (newsArr.length+1)
//更新状态
this.setState({newsArr:[news,...newsArr]})
}, 1000);
}
getSnapshotBeforeUpdate(){
// 上次列表高度
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps,preState,height){
// 每次更新后,通过差值计算出新item的高度,然后在当前位置的基础上增加最新的item高度,这样无论如何滚动都只停留在当前位置
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render(){
return(
<div className="list" ref="list">
{
this.state.newsArr.map((n,index)=>{
return <div key={index} className="news">{n}</div>
})
}
</div>
)
}
}
export default NewsList
脚手架
react 提供了一个用于创建 react 项目的脚手架库: create-react-app,项目的整体技术架构为: react + webpack + es6 + eslint,使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
创建项目
npx create-react-app demo
基础使用
public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>react</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
src/App.jsx
import React, { Component } from 'react'
export default class App extends Component {
render() {
return (
<p>hello world</p>
)
}
}
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App/>, document.getElementById('root'))
样式模块化
样式模块化可用于解决编译后样式冲突问题
- css使用module方式命名
- 在jsx中import对应的css文件
import React, { Component } from 'react'
import demo from './index.module.css'
export default class Demo extends Component {
render() {
return (
<p className={demo.title}>hello world</p>
)
}
}
配置代理
方式一
追加如下配置
package.json
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方式二
-
在src下创建配置文件setupProxy.js
-
编写setupProxy.js配置具体代理规则
src/setupProxy.js
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐
pubsub-js发布订阅
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
export default class Search extends Component {
search = () => {
PubSub.publish('SEARCH',{isFirst:false,isLoading:true})
}
}
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
export default class List extends Component {
componentDidMount(){
this.token = PubSub.subscribe('SEARCH',(_,stateObj)=>{
this.setState(stateObj)
})
}
// 卸载前取消订阅
componentWillUnmount(){
PubSub.unsubscribe(this.token)
}
}
setState
对象式的setState
setState(stateChange, [callback])
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
函数式的setState
setState(updater, [callback])
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
3.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
对象式的setState是函数式的setState的简写方式(语法糖)
使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取
lazyLoad
通过React的lazy函数配合import()
函数动态加载路由组件,路由组件代码会被分开打包,在渲染对应路由时加载对应组件。
import {lazy} from 'React'
const Login = lazy(()=>import('@/pages/Login'))
// 通过<Suspense>指定在加载得到路由打包文件前,显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
hooks
Hook是React 16.8.0版本增加的新特性/新语法,可以让你在函数组件中使用 state 以及其他的React特性
useState
React.useState(),让函数组件也可以有state状态, 并进行状态数据的读写操作
const [xxx, setXxx] = React.useState(initValue)
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
export const Demo = function() {
console.log('demo'); // 执行1+n次,第一次是初始化,n是点击次数
const [count, setCount] = React.useState(0)
function add() {
setCount(count + 1)
}
return (
<div>
<p>{count}</p>
<button onClick={add}>click</button>
</div>
)
}
useEffect
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)。
副作用函数:该函数执行会改变外部数据,是相对于纯函数而言的。
纯函数:该函数的执行不影响外部数据
副作用函数示例:
let obj = {age: 19}
function add(o) {
o.age += 1
}
add(obj)
console.log(obj)
useEffect(() => {
// 在此可以执行任何带副作用操作,相当于componentDidMount,render时先执行一次
// [stateValue]变化时也会执行,相当于componentDidUpdate()
return () => { // 如果返回一个函数,会在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等,相当于componentWillUnmount
}
}, [stateValue]) //如果不指定,监视所有的state的变化; 如果指定的是[],相当于不监视任何state变化; 如果有指定具体state值,那么只监视指定的值的变化
参数: 第一个参数是一个函数,用于执行任何带副作用的操作;第二个参数要监视变化的数据数组。
// 该组件每隔一秒加一
export const Demo = function() {
const [count, setCount] = React.useState(0)
// 卸载组件
function remove() {
ReactDOM.unmountComponentAtNode(document.getyElementById('root'))
}
React.useEffect(() => {
let timer = setInterval(() => {
setCount(count + 1)
}, 1000)
// 卸载组件前清楚定时器
return () => {
clearInterval(timer)
}
}, []); // 不监视任何数据,这样副作用函数只执行一次
return (
<div>
<p>{count}</p>
<button onClick={remove}>卸载组件</button>
</div>
)
}
useRef
Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据。功能与React.createRef()一样,写法有点差别。
export const Demo = function() {
const myRef = React.useRef()
function tip() {
alert(myRef.current.value)
}
return (
<div>
<input type="text" ref="myRef" />
<button onClick={tip}>点击提示数据</button>
</div>
)
}
Fragment
Fragment可以在写jsx时不用必须有一个真实的DOM根标签了,两种写法:
render() {
return (
<Fragment>
<p>hello</p>
<p>hello</p>
<Fragment>
)
}
render() {
return (
<>
<p>hello</p>
<p>hello</p>
</>
)
}
context
一种组件间通信方式, 用于【祖组件】与【后代组件】间通信。在应用开发中一般不用context, 一般都用它的封装react插件
import React, { Component } from 'react'
import './index.css'
//创建Context对象
const MyContext = React.createContext()
const {Provider,Consumer} = MyContext
export default class A extends Component {
state = {username:'tom',age:18}
render() {
const {username,age} = this.state
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={{username,age}}>
<B/>
</Provider>
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C/>
</div>
)
}
}
/* class C extends Component {
//声明接收context,该方式仅用于类组件
static contextType = MyContext
render() {
const {username,age} = this.context
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
</div>
)
}
} */
// 该方式用于函数式组件
function C(){
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:
<Consumer>
{value => `${value.username},年龄是${value.age}`}
</Consumer>
</h4>
</div>
)
}
组件优化
问题一
只要执行setState(),即使不改变状态数据, 组件也会重新render()
问题二
只要当前组件重新render(),就会自动重新render子组件,纵使子组件没有用到父组件的任何数据,因为Component中的shouldComponentUpdate()总是返回true
解决思路
只有当组件的state或props数据发生改变时才重新render()
方案一:重写shouldComponentUpdate()方法,比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false(有些麻烦)
方案二:使用PureComponent,PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true。注意这里的变化底层是浅比较,只改内部值不会触发更新。
import React, { PureComponent } from 'react'
export default class Parent extends PureComponent {
state = {
obj: {age: 18}
}
add = () => {
const obj = state.obj
obj.age = 19
// 不会触发更新,因为obj和state.obj是一个地址
this.setState(obj)
}
add2 = () => {
// 会更新
this.setState({obj: { age: 19}})
}
}
组件标签体内容
组件标签内容传递给子组件有两种方式:children props
和render props
,类似于vue中的slot。
children props
通过组件标签体传入结构
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
<A>
<B/>
</A>
</div>
)
}
}
class A extends Component {
render() {
console.log(this.props);
const {name} = this.state
return (
<div className="a">
<h3>我是A组件</h3>
{/* 标签体内容 */}
{this.props.children}
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="b">
<h3>我是B组件</h3>
</div>
)
}
}
这种方式比较简单,但是A组件无法向B组件传递数据
render props
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
{/* B是A的子组件 */}
<A render={(name)=><B name={name}/>}/>
</div>
)
}
}
class A extends Component {
state = {name:'tom'}
render() {
console.log(this.props);
const {name} = this.state
return (
<div className="a">
<h3>我是A组件</h3>
{/* B组件的位置,传递数据的位置 */}
{this.props.render(name)}
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="b">
<h3>我是B组件,{this.props.name}</h3>
</div>
)
}
}
名字不一定叫render
,习惯上用render属性名
错误边界
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面。只能捕获后代组件生命周期产生的错误(主要是render错误),不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
parent.jsx
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
state = {
hasError:'' //用于标识子组件是否产生错误
}
//当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息(开发环境下仍会显示错误页面)
static getDerivedStateFromError(error){
console.log('@@@',error);
return {hasError:error}
}
componentDidCatch(){
console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
)
}
}
Child.jsx
import React, { Component } from 'react'
export default class Child extends Component {
state = {
// users:[
// {id:'001',name:'tom',age:18},
// {id:'002',name:'jack',age:19},
// {id:'003',name:'peiqi',age:20},
// ]
users:'abc'
}
render() {
return (
<div>
<h2>我是Child组件</h2>
{
this.state.users.map((userObj)=>{
return <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4>
})
}
</div>
)
}
}
组件通信
组件间的关系有下面几种:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
组件间的通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubsub-js、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)