类式组件中的this
在类式组件中:如果用this来访问类式组件方法中的this,会出现undefined;
this的丢失
class Demo extends React.Component{
constructor (props) {
super(props)
console.log(this);
}
showData(){
console.log(this);//这里的this为undefined
}
render(){
return (
<div>
<input type="text" ref='input1' placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据 </button>
</div>
)
}
}
解决上面问题先看看DOM在渲染的时候做了什么?
//1.创建类式组件
class MyComponent extends React.Component {
render(){
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:',this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
//render在实例对象上被调用,this属于实例对象,可以访问类中自定义的任何属性或方法
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
拓展:
在构造器种的this,指的是类的实例对象。
在类里面定义的方法被放在了类的原型对象上,供实例使用。
而在上面代码种访问这个方法的时候不是通过实例对象来调用的,那这是什么原因?
class Person {
constructor(name,age){
this.name = name
this.age = age
}
study(){
//study方法放在了哪里?——类的原型对象上,供实例使用
//通过Person实例调用study时,study中的this就是Person实例 谁调用,this就指向谁
console.log(this);
}
}
const p1 = new Person('tom',18)
p1.study() // 通过Person实例调用study方法
// 类中定义的方法,类自身开启了局部严格模式,将study指向x,x为全局变量,应该指向window,此时又开启了严格模式,所以x()返回undefined
const x = p1.study
x()
//例如
function demo(){
'use strict'
console.log(this)
}
demo()//此时this为window
所以在执行<button onClick={this.showData}>点我提示左侧的数据 </button>
时并没有调用showData这个方法,而是直接通过原型链查找showData找到这个方法作为onClick的回调,在点击时直接从堆里面调用这个方法,在类里面方法默认开启了严格式,所以showData中的this为undefined
解决办法:
(一):在构造里面进行绑定该函数
showData里面的this不是实例对象的解决办法;
class Demo extends React.Component{
constructor (props) {
super(props)
this.showData=this.showData.bind(this)
//在这里的this是类的实例对象,通过在原型链上查找showData并且给他的this绑定了实例对象,然后将showData放在实例对象自身上,此时点击onClick事件,执行的是实例对象自身上的showData,这是showData里面的this就是原型对象了
}
showData(){
console.log(this);
}
render(){
return (
<div>
<input type="text" ref='input1' placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据 </button>
</div>
)
}
}
(二):自定义方法使用箭头函数
showData = ()=>{
const {input1} = this.refs
console.log(this);//此时的this为类的实例对象
alert(input1.value)
}
render(){
return (
<div>
<input type="text" ref='input1' placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据 </button>
</div>
)
}
state
类中使用state
在当前组件中定义状态时可以使用state,他是一个对象
在constructor里面初始化一个状态
class Demo extends React.Component{
constructor (props) {
super(props)
this.state = {
count: 1
}
}
addNumber = () => { // 进行加法操作
}
render(){
return (
<div>
<div>{this.state.count}</div>
<button onClick={this.addNumber}>点击加1 </button>
</div>
)
}
}
当忽略constructor时,可以直接在类里面定义state
class Demo extends React.Component{
state = { count: 1 }
addNumber = () => { // 进行加法操作
}
render(){
return (
<div>
<div>{this.state.count}</div>
<button onClick={this.addNumber}>点击加1 </button>
</div>
)
}
}
类里面:setState
一、参数一为函数对象, 有第二个回调函数
this.setState((count) => {count+1}, () => {
console.log(this.state.count)
})
无第二个回调函数
this.setState((count) => {count+1})
二、参数一为普通对象,有第二个回调函数
this.setState({
count:3
}, () => {
console.log(this.state.count)
})
无第二个回调函数
this.setState({count:3})
区别:第一个参数的区别:如果依赖原来的状态,使用函数对象,否则使用普通对象
在使用时,第一个参数为函数对象的可以写为下面方法,先拿到变量,在操作该变量
addNumber = () => {
const { count } = this.state;
this.setState({
count: count+1
})
}
第二个参数有无的区别:如果需要在setState()之后立即获取最新的状态数据, 则在第二个callback函数中读取
说明:先定义;在到需要使用的方法中解构出需要的属性
import React, { Component } from 'react'
export default class Demo extends Component {
state = { count: 1 } // -----初始化变量
addNumber = ()=>{
// ===> 后面表示执行顺序
//1.获取原来的count值
const {count} = this.state //-----获取变量
//2.更新状态,
// this.setState为异步的更新状态
//方式一:对象式的setState
// setState(stateChange, [callback])
// 第一个参数为对象,第二个参数为回调函数,解决异步问题
this.setState({count:count+1},()=>{
console.log(this.state.count); //===>2
})
console.log('输出',count); //0 ===>1 -----使用变量
//方式二:函数式的setState
// setState(updater, [callback])
// 第一个参数为可以接收到state和props,第二个为回调函数
this.setState( state => ({count:state.count+1}))
}
render() {
const { count } = this.state //从state中拿到count
return (
<div>
<h1>当前求和为:{count}</h1> //-----render中使用变量
<button onClick={this.addNumber}>点我+1</button>
</div>
)
}
}
总结:
在类式组件中,定义state对象可以在constructor构造器里面,也可以在constructor构造器外面
区别:是否需要加props中传过来的变量
修改state对象里面的变量,通过setState来修改,只有这样,才能使render重新渲染(无shouldComponentUpdate显示的前提)
如果要实现修改完state对象变量的值,在别的地方立即使用,可以使用setState的第二个参数
函数中使用state
在函数中可以使用React Hooks中useState
在使用前需要引入useState
import React,{ useState } from 'react'
第一步:初始化变量:
const [ 变量, 方法] = useState(初始值)
const [ count, addCount ] = useState(0)
第二步:使用
无论在render中还是在方法中使用,这个变量(count)在,最外层函数包裹的里面任何地方都是有效的
<div>{count}</div>
第三步:改变其值
方法里面传参数,传递参数的类型必须和定义初始值的类型相同
addCount(1)
这样count的值就改变为1了
完整使用hooks中useState的代码
import React, { useState } from 'react'
function Demo(){
const [count,setCount] = useState(0)// ------初始化一个变量
//加的回调
function add(){
//setCount(count+1) //第一种写法
setCount(count => count+1 )//第二中写法 ------开始更改值
}
return (
<div>
<h2>当前求和为:{count}</h2>//-------使用
<button onClick={add}>点我+1</button>
</div>
)
}
export default Demo
总结:
函数中不存在this,react代码经过Babel进行解析,Babel在函数中使用了 'use strict’严格模式,所以函数中的this为undefined
useState 就是一个 Hook,通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state。
useState 会返回一对值:当前状态和一个让你更新它的函数
Fragment
Fragment是一个空标签,用来包裹元素,在Fragment里面可以加入key属性
父组件
class Table extends React.Component {
render() {
return (
<table>
<tr>
<Columns />
</tr>
</table>
);
}
}
子组件
class Columns extends React.Component {
render() {
return (
<div>
<td>Hello</td>
<td>World</td>
</div>
);
}
}
最后渲染出来的结果
<table>
<tr>
<div>
<td>Hello</td>
<td>World</td>
</div>
</tr>
</table>
会发现多了一个div标签
此时就需要用到Fragment来代替多出来的div
子组件
class Columns extends React.Component {
render() {
return (
<Fragment>
<td>Hello</td>
<td>World</td>
</Fragment>
);
}
}
渲染之后的结果
<table>
<tr>
<td>Hello</td>
<td>World</td>
</tr>
</table>
也可以使用短语法来声明<></>
子组件
class Columns extends React.Component {
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
}
<></>
跟<Fragment>
不同的地方是<></>
不能添加任何一个熟悉,<Fragment>
可以添加一个key值(react官方说后期会加入其他属性)
比如:
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// 没有`key`,React 会发出一个关键警告
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
props
props解决了不同组件相互通信的问题
有时候对分离出来的组件与他的父组件进行数据交互,主要包括事件操作,数据共享
其中的一种办法就是使用props
这里有两个组件,一个父组件,一个子组件
父组件掌握数据存储,但是子组件想筛选数据这时候可以使用props操作
import Son from './son'
class Person extends React.Component{
state = {
data:[
name:'李四',age:19
]
}
eat = (data) => {
this.setState({data:data})
}
render(){
const data = this.state;
return (
<Son data={data},eat={this.eat} />
)
}
}
这里使用了通过父组件传递数据,子组件调用父组件的方法,来修改父组件的数据,进而更改子组件的数据
对从父组件传过来的值进行校验,得安装prop-types插件
class Son extends React.Component{
eat = () => {
this.porps.eat({name;"张三",age:15})
}
//对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男',//sex默认值为男
}
render(){
const {name,age} = this.props
return (
<div>
<span>{name}</span>
<span>{age}</span>
<button onClick={this.eat}></button>
</div>
)
}
}
例如要写一个todo列表
要添加下列功能:
功能一:在输入框里面输入内容,回车之后将输入的内容渲染在下面的列表里面,
功能二:新添加的事务默认的复选框是未勾选的,如果点击勾选,在底部会显示勾选了几项
功能三:鼠标移动到某一事务上面,会出现删除该事务的按钮,点击删除,该事物被删除,还可以点击最下面的删除已选中的,会删除掉已经勾选的事务。
图片描述:
代码设计理念:组件化设计,通过父组件作为代理接受子组件的事件,操作并分发数据
Header.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class Header extends Component {
//对接收的props进行:类型、必要性的限制
static propTypes = {
addTodo:PropTypes.func.isRequired
}
//键盘事件的回调
handleKeyUp = (event)=>{
//解构赋值获取keyCode,target
const {keyCode,target} = event
//判断是否是回车按键
if(keyCode !== 13) return
//添加的todo名字不能为空
if(target.value.trim() === ''){
alert('输入不能为空')
return
}
//准备好一个todo对象
const todoObj = {id:Math.random(),name:target.value,done:false}
//将todoObj传递给App
this.props.addTodo(todoObj)
//清空输入
target.value = ''
}
render() {
return (
<div >
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
)
}
}
List.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
export default class List extends Component {
//对接收的props进行:类型、必要性的限制
static propTypes = {
todos:PropTypes.array.isRequired,
updateTodo:PropTypes.func.isRequired,
deleteTodo:PropTypes.func.isRequired,
}
render() {
const {todos,updateTodo,deleteTodo} = this.props
return (
<ul >
{
todos.map( todo =>{
return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
})
}
</ul>
)
}
}
Item.js
import React, { Component } from 'react'
export default class Item extends Component {
state = {mouse:false} //标识鼠标移入、移出
//鼠标移入、移出的回调
handleMouse = (flag)=>{
return ()=>{
this.setState({mouse:flag})
}
}
//勾选、取消勾选某一个todo的回调
handleCheck = (id)=>{
return (event)=>{
this.props.updateTodo(id,event.target.checked)
}
}
//删除一个todo的回调
handleDelete = (id)=>{
if(window.confirm('确定删除吗?')){
this.props.deleteTodo(id)
}
}
render() {
const {id,name,done} = this.props
const {mouse} = this.state
return (
<li style={{backgroundColor:mouse ? '#ddd' : 'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
<label>
<input type="checkbox" checked={done} onChange={this.handleCheck(id)}/>
<span>{name}</span>
</label>
<button onClick={()=> this.handleDelete(id) } style={{display:mouse?'block':'none'}}>删除</button>
</li>
)
}
}
Footer.js
import React, { Component } from 'react'
export default class Footer extends Component {
//全选checkbox的回调
handleCheckAll = (event)=>{
this.props.checkAllTodo(event.target.checked)
}
//清除已完成任务的回调
handleClearAllDone = ()=>{
this.props.clearAllDone()
}
render() {
const {todos} = this.props
//已完成的个数
const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
//总数
const total = todos.length
return (
<div >
<label>
<input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total && total !== 0 ? true : false}/>
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button onClick={this.handleClearAllDone} >清除已完成任务</button>
</div>
)
}
}
App.js
import React, { Component } from 'react'
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'
export default class App extends Component {
//状态在哪里,操作状态的方法就在哪里
//初始化状态
state = {todos:[
{id:'001',name:'吃饭',done:true},
{id:'002',name:'睡觉',done:true},
{id:'003',name:'打代码',done:false},
{id:'004',name:'逛街',done:false}
]}
//addTodo用于添加一个todo,接收的参数是todo对象
addTodo = (todoObj)=>{
//获取原todos
const {todos} = this.state
//追加一个todo
const newTodos = [todoObj,...todos]
//更新状态
this.setState({todos:newTodos})
}
//updateTodo用于更新一个todo对象
updateTodo = (id,done)=>{
//获取状态中的todos
const {todos} = this.state
//匹配处理数据
const newTodos = todos.map((todoObj)=>{
if(todoObj.id === id) return {...todoObj,done}
else return todoObj
})
this.setState({todos:newTodos})
}
//deleteTodo用于删除一个todo对象
deleteTodo = (id)=>{
//获取原来的todos
const {todos} = this.state
//删除指定id的todo对象
const newTodos = todos.filter((todoObj)=>{
return todoObj.id !== id
})
//更新状态
this.setState({todos:newTodos})
}
//checkAllTodo用于全选
checkAllTodo = (done)=>{
//获取原来的todos
const {todos} = this.state
//加工数据
const newTodos = todos.map((todoObj)=>{
return {...todoObj,done}
})
//更新状态
this.setState({todos:newTodos})
}
//clearAllDone用于清除所有已完成的
clearAllDone = ()=>{
//获取原来的todos
const {todos} = this.state
//过滤数据
const newTodos = todos.filter((todoObj)=>{
return !todoObj.done
})
//更新状态
this.setState({todos:newTodos})
}
render() {
const {todos} = this.state
return (
<div>
<div>
<Header addTodo={this.addTodo}/>
<List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
<Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone}/>
</div>
</div>
)
}
}
index.js
//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom'
//引入App
import App from './App'
ReactDOM.render(<App/>,document.getElementById('root'))
refs
以下两个都是类式组件
- 字符串形式的
getRefValue = () => {
console.log(this.refs.input.value)
}
render() {
return (
<input ref="input" />
)
}
- 回调函数
getRefValue = () => {
const {input} = this
console.log(input.value)
}
render() {
return (
<input ref={ref => this.input = ref} />
)
}
使用回调函数式的ref,回调函数在组件挂载或者卸载的时候被调用,ref用于一个HTML元素的时候,ref指定的回调函数在调用的时候会接收一个参数,该参数就是指定的DOM元素,组件挂载的时候React会给ref回调函数传入当前的DOM,在组件卸载的时候会传入null
具体查看https://blog.csdn.net/liangklfang/article/details/72858295
- createRef创建ref容器
constructor (props) {
super(props)
this.inputRef = React.createRef()
}
getRefValue = () => {
console.log(this.inputRef.current.value)
}
render() {
return (
<input ref={this.inputRef} />
)
}
ref不止用在input输入框上,对于自定义的组件也可以使用,例如
父组件
constructor (props) {
super(props)
this.auth = React.createRef()
}
getAlldata = () => {
console.log(this.auth)
//通过给子组件添加ref,可以获得子组件上定义在this上的所有属性或方法
}
render(){
return(
<AuthForm role={role} ref={this.auth}/>
)
}
总结:
ref用在DOM元素上获取的是DOM元素的属性对象;用在组件上获取的是组件的实例对象
使用ref有三种方式:字符串形式,回调函数形式,使用createRef创建ref形式
生命周期
// 父组件
export default class ParentComponent extends React.Component {
static defaultProps = (function() {
console.log("ParentComponent: defaultProps");
return {
true: false
};
})();
constructor(props) {
super(props);
console.log("ParentComponent: state");
this.state = { text: "" };
this.onInputChange = this.onInputChange.bind(this);
}
componentWillMount() {
console.log("ParentComponent: componentWillMount");
}
componentDidMount() {
console.log("ParentComponent: componentDidMount");
}
componentWillUnmount() {
console.log("ParentComponent: componentWillUnmount");
}
onInputChange(e) {
const text = e.target.value;
this.setState(() => ({ text: text }));
}
componentDidCatch(err, errorInfo) {
console.log("componentDidCatch");
console.error(err);
console.error(errorInfo);
this.setState(() => ({ err, errorInfo }));
}
render() {
console.log("ParentComponent: render");
if (this.state.err) {
return (
<details style={{ whiteSpace: "pre-wrap" }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
);
}
return [
<h2 key="h2">Learn about rendering and lifecycle methods!</h2>,
<input
key="input"
value={this.state.text}
onChange={this.onInputChange}
/>,
<ChildComponent key="ChildComponent" name={this.state.text} />
];
}
}
// 子组件
import React from "react";
import PropTypes from "prop-types";
class ChildComponent extends React.Component {
static propTypes = {
name: PropTypes.string
};
static defaultProps = (function() {
console.log("ChildComponent : defaultProps");
return {};
})();
constructor(props) {
super(props);
console.log("ChildComponent: state");
this.state = {
name: "Mark"
};
this.oops = this.oops.bind(this);
}
componentWillMount() {
console.log("ChildComponent : componentWillMount");
}
componentDidMount() {
console.log("ChildComponent : componentDidMount");
}
componentWillReceiveProps(nextProps) {
console.log("ChildComponent : componentWillReceiveProps()");
console.log("nextProps: ", nextProps);
}
shouldComponentUpdate(nextProps, nextState) {
console.log("<ChildComponent/> - shouldComponentUpdate()");
console.log("nextProps: ", nextProps);
console.log("nextState: ", nextState);
return true;
}
componentWillUpdate(nextProps, nextState) {
console.log("<ChildComponent/> - componentWillUpdate");
console.log("nextProps: ", nextProps);
console.log("nextState: ", nextState);
}
componentDidUpdate(previousProps, previousState) {
console.log("ChildComponent: componentDidUpdate");
console.log("previousProps:", previousProps);
console.log("previousState:", previousState);
}
componentWillUnmount() {
console.log("ChildComponent: componentWillUnmount");
}
oops() {
this.setState(() => ({ oops: true }));
}
render() {
if (this.state.oops) {
throw new Error("Something went wrong");
}
console.log("ChildComponent: render");
return [
<div key="name">Name: {this.props.name}</div>,
<button key="error" onClick={this.oops}>
Create error
</button>
];
}
}
渲染过程:
开始加载时:
‘子组件默认props’-> ‘父组件默认props’-> ‘父组件constructor’-> ‘父组件componentWillMount’->‘父组件render’ -> ‘子组件constructor’-> ‘子组件componentWillMount’ -> ‘子组件render’ -> ‘子组件componentDidMount’ -> ‘父组件componentDidMount’
注意: 开始加载的时 父组件componentDidMount 修改的状态不会经过render
更新状态时:
‘父组件render’ -> ‘子组件componentWillReceiveProps’-> ‘子组件shouldComponentUpdate’- > ‘子组件render’ -> ‘子组件componentDidUpdate’ -> ’
挂载:
是React将组件插入实际DOM的过程
在组件进行挂载前(调用componentWillMount)进行改变状态,不会触发render函数,一旦挂载成功(调用componentDidMount),就可以进行更新状态了。
componentDidMount:当React调用这个方法时,就可以访问组件的refs、访问组件的状态、属性、以及组件更新的信息,也可以进行网络请求更新组件状态、也可以使用第三方库的地方。
更新:
更新中,用到了三个方法:shouldComponentUpdate,componentWillUpdate,componentDidUpdate
如果shouldComponentUpdate返回false,则render不会发生更新,此时组件不会更新,componentWillUpdate和componentDidUpdate也不会被调用
shouldComponentUpdate使用这个方法,可以进行性能优化,判断上一次的props或者state与下一次的进行比较,看是否要更新组件
总结: