jsx语法规则
- 定义虚拟dom时,不需要写引号 ' '
- 标签中混入js表达式,需要加{}
const myId = 'title'
const content = 'heelo,react'
const VDom = (
<div>
<h3 id={myId} className="title">
<span style={{fontSize="18px",color: 'red'}}>{content}</span>
</h3>
// 组件标签
<Good></Good>
</div>
)
- 标签中定义样式名要用className
- 标签中定义内联样式要style={{}}双大括号
- 所有标签外围只允许有一个根标签
- 标签必须闭合,组件标签首字母须大写
<Good></Good>
React定义组件的两种方式
1. 函数式组件
function MyComponent () {
return <h3>我是函数式组件</h3>
}
2. class类组件
class MyComponent extends React.Component {
render() {
return <h3>我是class定义的组件</h3>
}
}
// 当react调用类组件时,react做了什么?
// 1.根据组件名找到对应的类
// 2.react内部new了一个该类的实例对象,并且调用该实例对象原型上的render方法
// 3.调用render方法返回虚拟dom,创建真实dom渲染页面
备注:函数式组件适用于定义简单类型,无状态时的组件。 class类组件适用于定义复杂类型有状态时的组件
React 类组件中this指向问题
1.在react类组件中构造器中的this指向的是组件实例对象
2. render函数中的this也是指向组件实例对象
3. 类中定义的方法,挂载在实例对象的原型上的,当在render函数中调用方法时,并不是通过组件的实例对象调用的,而是直接调用的,在类中定义的方法默认开启严格模式,所以直接调用时this返回undefined
4. 只有通过组件实例对象调用类中的方法,this才指向该实例对象
// 解决办法
// (1). 在构造器调用bind函数生成一个新的函数挂载到组件实例上
constructor() {
this.newFuc = this.oldFuc.bind(this)
}
render() {
return <h3 onClick={this.newFuc}>点我改变</h3>
}
oldFuc() {
console.log('我是类中的方法')
}
//(2). 写成箭头函数 在类中通过赋值语法类似于 a=1 那么该类的实例对象上就会有a这个属性,oldFuc同理,由于箭头函数没有自身的this,所以this指向调用实例自身
oldFuc = () => {
console.log('我是类中的方法')
}
state属性
state:react存放数据的一个对象,react通过对state中数据的操作调用render函数更新视图
// 定义
class Demo extends React.Component{
// 1. 在构造器中定义
construtor() {
this.state = {name: '张三'}
}
// 2.直接定义 在类中通过赋值语法类似于 a=1 那么该类的实例对象上就会有a这个属性
state = {name: '张三'}
}
// 使用
changeState = () => {
// 通过setState()改变数据
this.setState({name: '李四'})
}
props
props: 用于父子间传递数据
// 基本用法
class Demo extends React.Component{
// static关键字,可以定义的属性是类自身的属性,不是类的实例对象身上的属性
static propTypes = {
name: PropTypes.string.isRequired, // 限制名字必须是字符串类型 必填
age: PropTypes.number, // 必须是数值类型
sex: PropTypes.string,
change: PropsTypes.func // 限制必须是一个函数
}
static defaultProps = {
sex: '男' // 默认值,当父组件没有传递时默认显示
}
render() {
const {name, age, sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
function change() {}
ReactDOM.render(<Demo name="张三" age={18} change={change}/>, document.getElementById("test"))
// 批量传递props参数 利用展开运算符
const p = {name:'张三',age: 18}
ReactDOM.render(<Demo {...p}/>, document.getElementById("test"))
函数式组件使用props
function Person(props) {
console.log(props)
return <h3>组件内容</h3>
}
//限制props参数类型 函数名.propTypes
Person.propTypes = {
name: PropTypes.string.isRequired, // 限制名字必须是字符串类型 必填
age: PropTypes.number, // 必须是数值类型
sex: PropTypes.string,
change: PropsTypes.func // 限制必须是一个函数
}
refs
ref: 在标签中给某个标签打个标识
// 用法(1),字符串形式获取ref标识,不推荐写法
class Demo extends React.Component{
// 弹出输入框值的方法
getVal = () => {
// 通过ref获取输入框值
const input1 = this.refs.input1
alert(input1.value)
}
render() {
return (
<input ref="input1" type="text"/>
<button onClick={this.getVal}>点我获取文本值</button>
)
}
}
// 用法(2),回调函数方式 原理,往实例对象自身绑定一个属性,值为ref标识的dom节点
class Demo extends React.Component{
// 弹出输入框值的方法
getVal = () => {
// 通过ref获取输入框值
const {input1} = this
alert(input1.value)
}
render() {
return (
// currentNode: 为ref标识的dom节点 在这里也就是input标签节点
<input ref={currentNode => this.input1 = currentNode } type="text"/>
<button onClick={this.getVal}>点我获取文本值</button>
)
}
}
// 用法(3),通过createRef函数创建
class Demo extends React.Component{
// 先创建
input1 = React.createRef()
// 弹出输入框值的方法
getVal = () => {
// 通过ref获取输入框值
alert(this.current.input1.value)
}
render() {
return (
<input ref={this.input1} type="text"/>
<button onClick={this.getVal}>点我获取文本值</button>
)
}
}
react中授控组件/非授控组件
受控组件就是可以被 react 状态控制的组件
在 react 中,Input textarea 等组件默认是非受控组件(输入框内部的值是用户控制,和React无关)。但是也可以转化成受控组件,就是通过 onChange 事件获取当前输入内容,将当前输入内容作为 value 传入,此时就成为受控组件。
好处:可以通过 onChange 事件控制用户输入,使用正则表达式过滤不合理输入
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
class Input extends Component{
constructor(){
super();
this.state = {val:''};
}
handleChange=(event)=>{
let val = event.target.value;
this.setState({val})
}
render(){
return (
<div>
<p>{this.state.val}</p>
<input type="text" value={this.state.val} onChange={this.handleChange} /> //input就是受控组件 被状态对象的属性控制
</div>
)
}
}
ReactDOM.render(<Input/>,window.app)
总结:受控组件可以理解为,通过绑定函数收集数据存放在state中,在需要用的地方从state中在获取(一般发送ajax请求),非受控组件,一般是在用的时候,直接获取数据在使用
React生命周期钩子函数(旧)
组件初次渲染调用生命周期
// 组件初次渲染调用生命周期
constructor(构造器执行)
⇓
componentWillMount(组件将要挂载)
⇓
render(初次渲染,及更新)
⇓
componentDidMount(组件挂载完毕)
组件更新调用的生命周期
// 当重新setState数据时调用。返回false时不会继续执行后面的钩子函数
shouldComponentUpdate(是否允许更新。有返回值,true or false,默认返回true)
⇓
componentWillUpdate (组件即将被更新)
⇓
render(更新)
⇓
componentDidUpdate (组件更新完毕)
组件卸载之前调用
ReactDom.unmountComponentAtNode() //触发
⇓
componentWillUnmount (组件即将被卸载)
特定情境下的生命周期
// 父子组件,父组件往子组件传递新的数据时会被调用,注意第一次传递不会调用,当数据改变重新传递会被调用,并且可以接收props参数
componentWillReceiveProps(props) (子组件将要接收新的父组件传来的数据)
React生命周期钩子函数(新)
初始化阶段
// 组件初次渲染调用生命周期
constructor(构造器执行)
⇓
getDerivedStateFromProps(得到一个派生的状态从props中) // !新增
⇓
render(初次渲染,及更新)
⇓
componentDidMount(组件挂载完毕)
更新阶段
getDerivedStateFromProps(得到一个派生的状态从props中) // !新增
⇓
shouldComponentUpdate(是否允许更新。有返回值,true or false,默认返回true)
⇓
render(更新)
⇓
getSnapshotBeforeUpdate (在更新之前再次钩子中可以记录上一次的状态) // !新增
⇓
componentDidUpdate (组件更新完毕)
卸载阶段
ReactDom.unmountComponentAtNode() //触发
⇓
componentWillUnmount() // 即将被卸载
getDerivedStateFromProps(props)
- 该钩子函数接收父组件传递回来的props参数,使用时前面需要加
static关键字
并且需要返回一个对象- 如果一个组件中的state数据完全取决于父组件传递的props可以用该函数,因为一旦定义该函数返回同名的state状态值,那么该组件中的state将不会改变,以该函数返回的为准
state = {name: '张三'} // 使用该钩子 static getDerivedStateFromPorps(props) { return {name: '李四'} }
getSnapshotBeforeUpdate()
- 该函数在render()之后调用,但调用时页面还未真正发生更新,在该函数中可以返回更新前的数据及页面状态
- 该函数必须有返回值,可以返回更新前状态,并且在
componentDidUpdate()
函数中可以接收到返回的参数,也可以返回`nullgetSnapshotBeforeUpdate() { return this.ref.input.value } // 接收返回值 componentDidUpdate(prevProps, prevState, value) { // prevProps: 更新前的props // prevState:更新前的state // value: getSnapshotBeforeUpdate 返回值 }
React 钩子函数新旧对比
- 与新的生命周期相比,react废弃了三个钩子函数
- <font color=red>componentWillMount </font> <font color=red>componentWillUpdate</font> <font color=red>componentWillReceiveProps</font>
- 新增了两个生命周期
- <font color=green>getDerivedStateFromProps</font> <font color=green>getSnapshotBeforeUpdate</font>
React脚手架创建项目
npx create-react-app 项目名
// 创建项目
React中样式的模块化
一般在开发过程中,每一个组件都会新建一个文件夹,里面包含该组件引用的样式、图片等资源,但如果该组件用样式名和别的组件相同,会导致样式被覆盖的问题,所以需要样式模块化解决。或者通过less,sass等样式嵌套编写解决冲突。
// Hello组件中有index.jsx 和 index.css 两个文件 index.jsx index.css // 使用模块化,css文件命名需要加module index.css => index.module.css // 在index.jsx中引入 import hello from './index.module.css' // 在组件用使用样式,需要加上前缀 <div className={hello.title}></div>
React脚手架配置跨域代理
一、方法一:在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
二、方法二: 在src下创建配置文件:
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': ''} }) ) }
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
React 路由的使用
1.安装路由
npm i react-router-dom
2. 路由的基本使用
明确路由的导航区域和展示区域
导航区的a标签使用Link标签代替
<Link to="xxx">导航一</Link>
展示区写Route匹配路径及组件
<Route path="xxx" component={Home} />
入口文件App.js 中
<App/>
组件外侧包一个
<BrowserRouter> <App/></BrowserRouter>
或者<HashRoute></HashRoute/>
import {Link,Route} from 'react-router-dom' // 导航区 <Link to="xxx">xxx</Link> // 展示区 <Route path="xxx" component={组件名}/>
React 一般组件和路由组件区别
- 写法不同
// 一般组件 import Demo from './component/Demo' <Demo/> // 路由组件 <Route path="demo" component={Demo}/>
接收到的props不同
<font color="blue">一般组件收到的props取决去父组件传递的值</font>
<font color="blue">路由组件能够接收到固定三个属性:<font color="#f47920">history location match</font></font>
路由 NavLink的使用
<font color="blue">使用NavLink 自定义选中时的高亮效果,通过
activeClassName
绑定自定义类名</font>
import {NavLink} from 'react-router-dom'
<NavLink activeClassName="active" to="xxx">导航一</NavLink>
Switch 使用
<font color="blue">在react-router(路由)中假如展示区路由出现两个同名的路径,react-router默认会两个组件都展示,为了解决这个问题,在<Route></Route>外围包括一层<Switch></Switch>路由一旦匹配上一个不会再往下继续匹配</font>
import { Switch } from 'react-router-dom' <Switch> <Route path="xxx" component={xxx} /> <Route path="xxx" component={xxx} /> // 只会展示第一个 </Switch>
exact 开启路由严格匹配
<font color="blue">默认情况下react-router会进行模糊匹配,遵循最左原则前缀,也就是说路由路径是从左边开始匹配,如果有多级路由比如:/a/b/c 会把该路径中 a、b、c分别拿出来一旦匹配上a的路由就算成功不在看b、c</font>
<font color="#f15a22">在Link中开启严格匹配模式只需加上exact</font>
说明:非必要情况不需要开启严格模式,严格模式会导致嵌套路由时,二级路由不生效
<Route exact path="/a/b/c" component={xxx}></Route> // 只有link to="/a/b/c" 才会匹配该路由组件
Redirect 路由重定向
import { Redirect } from 'react-router-dom'
// 当所有路由无法匹配时,默认重定向一个路由组件
<Redirect to="/home" />
路由传递参数
1. params方式传递
// 1.传递 如果传递动态的值需要es6模板字符串表达式,需要加{}
<Link to={`/home/message/${id}/${title}`}></Link>
// 2.路由标签声明接收(必须)
<Route path="/home/message/:id/:title" component={xxx} />
// 3.组件获取参数
this.props.match // params传递的参数会包含在props.match中
2. search(query)方式传递
// 1.传递参数
<Link to={`/home/message?id=${id}&title=${title}`}></Link>
// 2. 路由无需声明接收
<Route path="/home/message" component={xxx} />
// 3. 组件获取参数
this.props.location.serach // search: "?id=001&title=消息1"
3. state 方式传递
// 1. 传递方式 需要写成{}对象的方式
<Link to={{pathname:'/home/message/detail',state:{id:'001',title:'消息'}}}></Link>
// 2. 路由无需声明接收
<Route path="/home/message" component={xxx} />
// 3.组件获取参数
this.props.location.state
备注:
- params 和search传递参数会暴露在url中,state方式则不会
- params和 search方式刷新浏览器页面不会丢失,state方式在HashRoute模式下会丢失(原理:state虽然浏览器url没有记录参数值,实际上还是借助了浏览器的history记录了参数所以在histtory模式下刷新不会丢失参数,但在Hash模式下就会丢失参数,从而找不到页面)
编程式路由导航
history模式下的路由,每个组件中props都包含三大对象,history,location ,match 编程式导航借助history中api操作页面跳转
<button onCilck={()=>this.pushShow}>编程式导航</button> // 定义方法 pushShow = () => { // params方式 this.props.history.push(`/home/message/detail/${id}/${title}`) // search 方式 this.props.history.push(`/home/message/detail?id=${id}&title=${title}`) // state 方式 this.props.history.push('/home/message/detail',{id:id,title: title}) }
说明: push(path,state),replace(path,state) 接收两个参数,path: 跳转路径 state: 携带state参数是个对象{}
withRouter使用
非路由组件不具备路由组件跳转等api
<font color="blue">withRouter: 用于把一般组件加工返回一个具备路由组件Api的新组件</font>
import { withRouter } from 'react-router-dom' class Header extends Component { render(){ return } } export default withRouter(Header) // 把Header组件加工后返回新的组件具体路由组件api
redux 基本使用
安装 npm i redux
在项目src下创建redux文件夹
在redux文件夹下创建
store.js
action.js
consturst-type.js
reducer.js
<font color="blue">store.js 文件</font>
// store.js 引入redux中createStore函数创建reudx管理仓库 import {createStore} from 'redux' //引入 reducer import reducer from './reducer' // 创建并暴露store export default createStore(reducer)
<font color="blue">reducer.js 文件</font>
// reducer 本质上还是一个函数,接收两个参数:preState(上一次的状态) action(type,data) 并返回新的状态 // 初始化和处理状态,初始化store会自动调用一次 preState值为undefined import {INCREMENT, DECREMENT} fromm './consturst-type' function xxx(preState, action) { const {type, data} = action switch (type){ case INCREMENT: return preState * data default return preState } }
<font color="blue">consturst-type.js 文件</font>
// 用来约束和防止方法过多时难以维护 export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
<font color="blue">action.js 文件</font>
// 定义和封装页面提交触发的action函数,方便代码阅读维护 import {INCREMENT, DECREMENT} fromm './consturst-type' export const add = data => ({type: INCREMENT, data})
说明: 当函数需要返回一个对象时,不写{return xxx} 需要用({})包括
页面获取state及使用action
// 页面使用 import store from './redux/store' import { add } from '.redux/action' // 获取redux中state的值 <div>{store.getState()}</div> // 调用action方法 increment = (data)=> { // 调用dispatch方法触发 store.dispatch(add(data)) }
redux-thunk (异步action中间件)
安装
npm i redux-thunk
- 作用: 用于开启异步action ,在action中执行异步操作
- 使用规则,在store.js中引入
// 创建redux store仓库,用于管理所有的reducer // 引入redux // applyMiddleware 该函数作为createStore函数第二个参数用于使用中间件 import { createStore, applyMiddleware } from 'redux' // 引入reducer import countReducer from './countReducer' // 引入中间件开启异步action import thunk from 'redux-thunk' // 暴露并创建Store export default createStore(countReducer, applyMiddleware(thunk))
- 在action.js 中写法如下
export const asyncAdd = data => { // 异步action返回值为一个函数(function), 同步action为一个对象({}) // 默认接收dipatch return (dispatch) =>{ // 执行异步任务 setTimeout(() => { // 异步调用同步返回一个对象,reducer中处理只能是对象 dispatch(increment(data)) }, 500); } }
react-redux 使用
安装
npm i react-redux
作用:用于连接redux和UI组件
- 所有UI组件都应该被包裹在容器组件中,它们是父子关系
- 容器组件是真正和redux打交道的,可以操作redux的api
- UI组件不能使用任何redux的api
- <font color="blue">容器组件会传给UI组件:(1)redux中保存的状态 (2)用于操作reudx状态的方法</font>
- 容器组件给UI组件传递状态、操作方法均通过props传递
如何创建一个容器组件?
创建容器钻进需要靠react-redux中的connect函数
import { connect } from 'react-redux' import ConutUI from './component/Count' // UI组件 export default connect(mapStateToProps, mapDispatchToProps)(ConutUI)
connect 函数接收两个参数:
<font color="blue"> mapStateToProps(state):</font>映射redux中的状态,返回值是一个对象{}
<font color="blue"> mapDispatchToProps(dispatch):</font>映射操作状态的方法,返回值是一个对象{}
<font color="blue"> 备注:容器组件中的store是靠props传进去的,不是组件直接引入</font>
// 父组件 import Count from './container/Count' //容器组件 import store from './redux/store' // 使用 <Count store = {store}/>
react-redux中间件写法优化
1、开启自动检测状态更新
在没有使用react-redux时,redux管理的状态state更新后视图并不会自动更新,需要用store.subscribe函数检测状态的更新
// 默认redux状态改变后不会更新页面值,这里在根组件挂在完毕后检测状态时候改变,状态一旦改变调用setState触发render()重新刷新页面 // index.js 不用react-redux需要这样监测 store.subscribe(()=>{ ReactDom.render(<App />, document.getElementById("root")); }) // 使用react-redux后 // 入口文件 import React from "react"; import ReactDom from "react-dom"; import { Provider } from "react-redux"; import App from "./App"; import store from "./redux/store"; ReactDom.render( // 使用react-redux中Provider组件包裹根组件,可以让后代容器组件自动监测redux中状态的改变刷新页面 <Provider store={store}> <App /> </Provider>, document.getElementById("root") );
2. 组件内connect函数简写
react-redux创建一个容易组件需要借助connect函数,connect()接收两个参数
<font color="blue">mapStateToProps(state):</font>映射redux中的状态,返回值是一个对象{}
<font color="blue">mapDispatchToProps(dispatch):</font>映射操作状态的方法,返回值是一个对象{}
function mapStateToProps(state){ // state redux管理的状态 return {key: value} } function mapDispatchToProps(dispatch) { return { // 里面写操作状态的方法action } }
简写
import React, { Component } from "react"; import { connect } from 'react-redux' import { increment, decrement, asyncAdd } from "../redux/countAction"; class Count extends Component { // 调用reducer进行加法操作 increment = () => { const { value } = this.selectNode; this.props.add(value * 1) }; // 调用reducer进行减法操作 decrement = () => { const { value } = this.selectNode; this.props.reduce(value * 1) }; //异步提交action asyncAdd = ()=> { const { value } = this.selectNode; this.props.addAsync(value * 1) } render() { console.log(this.props); return ( <div> {/* 获取store的值 */} <h2>值:{this.props.sum}</h2> <select ref={c => (this.selectNode = c)}> <option value={1}>1</option> <option value={2}>2</option> <option value={3}>3</option> </select> <button onClick={this.increment}>点我+1</button> <button onClick={this.decrement}>点我-1</button> <button onClick={this.asyncAdd}>点我等500ms+1</button> </div> ); } } // mapStateToProps // 声明组件需要使用的状态 供上面组件使用 const CountState = state => ({sum: state}) // mapDispatchToProps const CountDispacth = { // 声明组件需要操作的action方法 供上面组件使用 add: increment, reduce: decrement, addAsync: asyncAdd } // 把count组件包装一层变成容易组件暴露出去 export default connect(CountState, CountDispacth)(Count)
合并reducer
一个应用有很多模块,每个模块有对应的state和reducer,数据共享时往往需要在store里把多个reducer合并成一个对象
// store.js // combinRecuders函数 合并多个reducer,传入一个对象 import {createStore, applyMiddleware, combinRecuders} from 'redux' // 引入中间件开启异步action import thunk from 'redux-thunk' import { person } from './person/reducer' import { count } from './count/reducer' // 合并多个reducer const allRecuder = combinRecuders({ count: count, person: person }) export default createStore(allRecuder, applyMiddleware(thunk))
合并之后再组件内引用
import React, { Component } from "react"; import { connect } from "react-redux"; import { add_person } from "../redux/person/action"; import { nanoid } from "nanoid"; class Person extends Component { addPerson = () => { const name = this.nodeName.value; const age = this.nodeAge.value; const newPerson = { id: nanoid(), name, age }; this.props.add_person(newPerson); }; render() { return ( <div> <div>上面计数的和:{this.props.count}</div> <input ref={c => (this.nodeName = c)} type="text" placeholder="请输入姓名" /> <input ref={c => (this.nodeAge = c)} type="text" placeholder="请输入年龄" /> <button onClick={this.addPerson}>提交添加</button> <hr /> <ul> {this.props.person.map(p => { return ( <li key={p.id}> 姓名:{p.name}----年龄:{p.age} </li> ); })} </ul> </div> ); } } // 容器包装 export default connect( // state redux管理的总状态 state => ({ person: state.person, count: state.count }), // 映射状态 {add_person} )(Person);
react-router-dom(路由懒加载)
react中路由组件未使用懒加载在第一次请求时默认会加载所有路由组件的js,如果路由组件很多就有产生效率问题,懒加载就是为了解决这一问题,当点击要进入的路由时,才去加载该路由资源,提高了效率。
实现:
import React, {Component, lazy} from 'react' // 利用react上的lazy函数实现懒加载 const Home = lazy(()=> import('./Home'))
Suspense组件的使用:当路由进行懒加载时,点击某个路由可能需要一段时间才能加载完毕显示对应内容,该组件可以指定一个组件,在路由组件加载完毕之前显示,一般用于Loading动态加载。
实现:
import React, {Component, lazy, Suspense} from 'react' // 利用react上的lazy函数实现懒加载 const Home = lazy(()=> import('./Home')) import Loading from './component/Loading' // 该组件使用需要包裹Route外侧 通过fallback={<Loading />}指定要加载的组件 <Suspense fallback={<Loading />}> <Route path="/home" component={Home} /> </Suspense>
Hooks
1. useState
背景:react函数式组件因为没有this没有实例对象,无法使用state等,16.8.0版本之后为了解决这个问题引入hooks函数,可以让函数式组件使用state等类式组件拥有的api
使用:
import react,{useState} from 'react' function App() { // useState接收两个参数,第一个为传入的初始值,第二个是操作该值的方法 const [count, setcount] = useState(0) const [name, setName] = useState('张三') function add() { setcount(count+1) } function chnageName() { setName('jack') } return ({ <div> <div> count:{count}, 名字: {name} </div> <button onClick={add}>点我加1</button> <button onClick={chnageName}>点我改名字</button> </div> }) }
2. useEffect
作用:监测useState声明的值,同时具有部分生命周期特性
接收两个参数useEffect(function, []) 第一个是函数,第二个是数组[],不写第二参数时默认监测所有state改变,要监测某个值需要在数组中声明
相当于三个生命周期函数:componentDidMound componentDidUpdate componentWillUnmount
import react,{useState, useEffect} from 'react' function App() { // useState接收两个参数,第一个为传入的初始值,第二个是操作该值的方法 const [count, setcount] = useState(0) function add() { setcount(count+1) } function chnageName() { setName('jack') } useEffect(() => { // 这里的代码组件挂在完毕和count状态更新后都会调用 },[count]) return ({ <div> <div> count:{count} </div> <button onClick={add}>点我加1</button> </div> }) }
3. useRef
在函数式组件中使用ref 需要通过useRef()函数定义
import React, { useState, useEffect, useRef } from "react"; export default function App() { // 声明一个ref标识 const myInput = useRef() function chnageName() { console.log(myInput.current.value); } useEffect(() => { console.log(2); }, [name]); return ( <div> // 使用ref标识 <input type="text" ref={myInput}/> <button onClick={chnageName}>点我改名字</button> </div> ); }
4. useContext
主要用于祖孙组件之间传值,跨组件传值
用法:
- 一般会在src文件夹下定义一个Hooks文件夹,里面定义useContext文件
// Hooks/useContext.jsx import { createContext } from 'react' // 声明并暴露一个context上下文 export const myContext = createContext()
- 在需要的父组件中引入声明好的context
// 父组件引入 import {useState} from 'react' import {myContext} from '../Hooks/useContext' // 子/孙组件 import Son from './Son' const [count, setCount] = useState(99) function App() { return ( <div> {/* 使用时借助myContext中的Provider value:需要传递的值 */} <myContext.Provider value={count}> <Son /> </myContext.Provider> </div> ) }
- 在子/孙组件获取使用
import {useContext} from 'react' // 引入要使用的上下文对象 import {myContext} from '../Hooks/useContext' function Son() { // 获取上下文中传入的对象 const count = useContext(myContext) console.log(count) }
Fragment
作用:因为每个组件必须有一个根节点div包括,组件嵌套多层会引起dom嵌套太多,在开发中却不是必要的节点,Fragment标签包括一组node节点在渲染成真是dom时,这层会被丢弃减少不必要的嵌套
该标签只有一个标签属性key,可以用在循环遍历生成dom时
import React,{Fragment} from 'react' function App() { return ( <div> <Fragment key={1}>这成内容在渲染成真是dom时会直接渲染在外侧div下</Fragment> </div> ) }
类式组件优化
在组件中,当有组件嵌套时,当父组件状态改变引发组件重新render更新时,其组件的所有子组件都会跟着render更新,这就导致了效率的问题,解决方案如下:
- 利用
shouldComponentUpdate
该生命周期可以控制是否render
shouldComponentUpdate(nextProps, nextState)
接收两个参数,nextProps 下一次更新的props,nextState: 下一次更新的state, 在该生命周期中可以比较要更新的props和state的值是否和之前一样,返回ture或false控制是否需要render
PureComponent
react内部封装了控制是否更新的方法,只需要类式组件extendsPureComponent
import React, {PureComponent} from 'react' class App extends PureComponent { }
说明:PureComponent 内部只是进行了浅比较,也就是说复杂数据类型,如对象和数组形式的数据,如果想要重新引起render必须创建一个新的值,换而言之,对象的引用不变react就会认为该state没有改变不会重新render。
renderProps
要形成父子组件,除了在父组件内直接引入子组件写标签外,还可以在父组件使用的时候在传入要引用的子组件
// App.jsx class App extends Component { render(){ return ( <div> // 组件A.jsx // 在A组件使用时定义属性render是一个函数返回值为一个组件 <A render={(name)=> <B name={name}/>}/> </div> ) } } class A extends Component { state = {name: '皎月'} render() { const {name} = this.state return ( <div> 我是A组件... // 占位符 调用父组件中传递的render回调函数把自身state传出去 {this.props.render(name)} </div> ) } } class B extends Component { render() { return ( <div> 我是B组件... {this.props.name} </div> ) } }