React扩展

类式组件中的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

以下两个都是类式组件

  1. 字符串形式的
getRefValue = () => {
	console.log(this.refs.input.value)
}
render() {
	return (
		<input  ref="input" />
	)
}

  1. 回调函数

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

  1. 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与下一次的进行比较,看是否要更新组件
总结:在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值