事件
绑定事件
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
class Hello extends Component {
constructor(){
super()
this.state = {}
}
// 绑定事件需要使用驼峰的方式
render(){
return (
<div onClick={ function(){console.log('ok')}}>
Test
<button onClick={this.myClick}>按钮1</button>
<button onClick={()=> this.show('传参')}>按钮2</button>
</div>
)
}
// <button onClick={this.myClick()}></button>
// 如果加小括号 等于直接调用 不需要点击的
// 因为render也在生命周期中 使用这个组件的时候 就相当于创建了一个实例
// 这个组件实例必然就会调用render函数 必然就会解析return 的虚拟dom
// 在解析这个应该存放变量的地方的时候 这个函数没有return值 但是这行必然会随着解析而执行
// 所以这么写 不点击也会触发
myClick(){
console.log('ok2')
}
show = (arg1)=>{
console.log(arg1) // '传参'
}
// show这种绑定事件的方法最常用 在jsx必须使用箭头函数
}
事件绑定2
也可以使用bind返回一个函数,用变量接受,在虚拟dom中直接this.xxx
class Parent extends React.Component {
constructor(props) {
super(props);
// handleClick 当做函数指针
this.handleClick = this.handleClick.bind(this);
}
render(){
return <div onClick={this.handleClick}></div>
}
handleClick(){
console.log('111')
}
}
使用this.setState修改state上的值
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
class Hello extends Component {
constructor(){
super()
this.state = {
msg:"哈哈哈",
num: 1
}
}
render(){
return (
<div>
<button onClick={()=> this.show()}>按钮2</button>
<p> { this.state.msg }</p>
</div>
)
}
show = () => {
// 不能直接赋值来修改(不会立即渲染视图) 应该使用setState方法
// this.state.msg = '你好'
this.setState({msg:"你好"})
// 只会修改对应的状态msg 并不会影响到state中的num属性
console.log(this.state.msg) // '哈哈哈'
// 因为 this.setState() 是异步更新DOM的 console语句会率先执行完毕
// 如果想立即使用修改后的值 应该使用setState的第二个参数:回调函数
// 这个回调函数修改完成后 被调用 == vue.$nextTick()
this.setState({msg:"show"},function(){
console.log(this.state.msg) // show
})
}
}
React的数据绑定
// React 默认只有单向数据流 没有像v-model那样的双向数据绑定
// React中 如果将表单元素中的value与state中的数据绑定
// 当状态(state中的数据)变化的时候 --> 会触发页面的更新
// 但是 在页面触发改变的时候(文本框输入的时候) 不会同步state中的数据更新
// 需要程序员绑定onChange事件拿到最新的文本框中的值
// 手动调用this.setState()去更新state中的数据
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
class Hello extends Component {
constructor(){
super()
this.state = {
msg:"哈哈哈",
}
}
render(){
return (
<div>
<input type="text" value={this.state.msg} onChange={(e)=>this.changeValue(e)} ref="txt"/>
<input type="text" ref={el=>this.myInput = el}/>
</div>
)
}
// 设置ref为字符串是过时了的API 应该设置为一个回调函数
changeValue = (e)=> {
this.setState({
msg: e.target.value
})
// 或者使用ref属性暴露dom元素
// this.setState({
// msg: this.refs.txt.value / this.myInput
// })
}
}
React组件的生命周期
// 生命周期函数
// 组件创建阶段 : 只执行一次
componentWillMount(){}
render(){}
componentDidMount(){}
// 组件运行阶段 : 根据状态的改变触发0或多次
componentWillReceiveProps(){}
shouldComponentUpdate(){}
componentWillUpdate(){}
render(){}
componentDidUpdate(){}
// 组件销毁阶段 : 只执行一次
componentWillUnmount(){}
流程
1.创建时期,第一个执行的函数是constructor()
,定义state
2.组件挂载之前触发componentWillMount
,此时页面还未渲染。
3.执行render
函数,创建虚拟dom,将虚拟dom挂载到真实dom上去
4.完成挂载时,触发componentDidMount
函数,页面已经被渲染。
5.1 如果属性(props)改变,接受的传参改变了,触发componentWillReceiveProps
函数,然后触发shouldComponentUpdate
函数
5.2 如果状态(state)改变,直接触发shouldComponentUpdate
函数
6.1shouldComponentUpdate
函数如果return false
重新回到运行中的状态,页面没有任何更新
6.2shouldComponentUpdate
函数如果return true
即代表页面即将发生更新,继续走下面的流程,不定义默认返回true
7.组件更新前,触发componentWillUpdate
函数。
8.执行render
函数,比较更新虚拟dom,重新渲染页面
9.组件完成更新后,触发componentDidUpdate
函数
10.组件被销毁之前触发componentWillUnmount
函数,调用ReactDOM.unmountComponentAtNode(document.getElementById('app'))
可以销毁一个组件。
注意
1.this.forceUpdate()
强制刷新会跳过shouldComponentUpdate
钩子函数
2.父组件只要调用setState
函数 子组件也会更新 不管子组件有没有使用到父组件中的数据 但是可以在子组件中的shouldComponentUpdate
进行拦截
3.因为父组件状态改变了 如果没有在shouldComponentUpdate
中比较拦截 就会走下面的流程 必然会再次触发render
函数
4.触发了render函数 子组件就会触发更新 componentWillReceiveProps
钩子就会触发
5.PureComponent
可以避免这一点,如果子组件都没有用到父组件中的数据 那么子组件比对state
必然没有改变
6.不要在shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
函数中调用setState
就会造成死循环
PureComponent
根组件在setState的时候会触发render函数,也会同时触发所有组件的更新,这是不愿意看到的,也会影响性能。
父组件有时候的更新状态是希望子组件更新的,有时候是不希望的(没有修改影响子组件的状态的时候),所以需要在shouldComponentUpdate中具体分析
class myCom extends React.PureComponent {
}
// PureComponent 与 Component 的差异 体现在shouldComponentUpdate上
// PureComponent 会将nextState 和 nextProps (shouldComponentUpdate的参数)
// 与当前的 state 和 props 去比较 如果不同 则返回true 如果相同就返回false
// 而Component的 shouldComponentUpdate 默认返回true
// 但是 PureComponent 也只比较 state 和 props 的第一层直系属性
// 因为 如果程序员 使用state定义了一个嵌套很深的json 此时一层层去比较就会太浪费性能
父子组件通过props传值元素(使用函数)
class ChildInput extends Component {
render(){
let pInput = this.props.parentIpt //2.2子组件接受父组件暴露的元素
return (
<input type="text" ref={el=>this.props.getChildInput(el)} />
// 1.2子组件通过el传值给props接受到的函数 将input暴露给父组件 ref中函数的形参就是本身元素的指针el
)
}
}
class ParentInput extends Component {
render(){
// 1.1通过传递一个赋值接受ref的函数给子组件
return (
<div>
<input type="text" value="2.1 父组件把自己的dom元素暴露给子组件" ref={el=>this.myInput =el}/>
<ChildInput getChildInput={this.getChildInput.bind(this)}
parentIpt = {this.myInput} ref={el=>this.chilidComp=el}>
</ChildInput>
<button onClick={this.setFocus.bind(this)}>点击让子组件的input聚焦</button>
</div>
)
// 3.3 可以暴露这个组件给父组件 像上面的ref={el=>this.chilidComp=el}一样 但是不介意使用
// 因为这样 父组件中的chilidComp这个变量代表了整个子组件 会占用很大的空间 性能不好
}
getChildInput(el){
// 1.3子组件执行render的时候 就会触发传过去的getChildInput函数 把el作为参数传到父组件
// 这样父组件就取到了子组件暴露的dom元素 然后赋值给自己的变量
this.cInput = el;
}
setFocus(){
this.cInput.focus()
}
}
父子组件内容分发
class Child extends Component {
render(){
return (
<div>子组件的div
{this.props.children[1]} --- 这是span元素
{this.props.children[0]} --- 这是p元素
<hr/>
{this.props.children} ---将整个分发的内容放在这里
</div>
)
}
}
class Parent extends Component {
render(){
return (
<div>
<Child>
<p>父组件中的p元素</p>
<span>父组件中的span元素</span>
</Child>
</div>
)
}
// 上面这种写法 p/span元素是不会分发到子组件中区的
// 因为 这个子组件会整个代替渲染父组件中的Child组件的位置
// 子组件没有p/span元素 所以要在子组件中接受父组件的分发
}
// 或者传一个虚拟dom对象给子组件 做分发
class Child extends Component {
render(){
return (
<div>子组件的div
{this.props.headDom} --- 展示父组件给的h1
</div>
)
}
}
class Parent extends Component {
render(){
return (
<div>
<Child headDom={<h1> 给你一个h1去展示吧 </h1>}>
</Child>
</div>
)
}
}
Context -- 用于嵌套过深的组件传值 避免一层层的传递
// 有三个组件 A 包含 B ,B包含 C
// 如果A组件想传值给C组件 要使用props传给B 再传给C
// 这种传值 可以使用 Context 来简化
const ValContext = React.createContext('默认值')
// 或者
const {Provider,Consumer} = React.createContext('默认值')
// 这样下面 就可以使用Provider Consumer 来充当根元素 而不是 ValContext.Provider
// 创建Context组件 充当第一层级元素的根元素 并使用ValContext.Provider 供应者
// 并充当需要接受数据的组件的根元素 并使用ValContext.Consumer 消费者
class Ccom extends Component {
render(){
return (
<ValContext.Consumer>
展示从A传下来的值 函数中的参数value就是传来的值
函数返回一个虚拟dom元素 就可以展示传来的值在该组件上了
{value=> <p>我接受到A传来的值 : {value}</p>}
</ValContext.Consumer>
)
}
}
class Bcom extends Component {
render(){
return (
<Ccom></Ccom>
)
}
}
class Acom extends Component {
render(){
return (
<ValContext.Provider value="我要传给c的值">
<Bcom></Bcom>
</ValContext.Provider>
)
}
}
Fragments 可以聚合一个子元素列表 并不添加额外节点
看起来就是一个空标签,类似vue中的template充当根标签 但是不增加额外节点
render(){
return (
<>
<ChildA/>
<ChildB/>
</>
)
}
// 或者 因为空标签语法不接受 key 如果你要遍历的话 就会报警告
// React.Fragment 能够接收key 也是唯一只能接受key
render(){
return (
{
this.list.map(item=>(
<React.Fragment key={item.id}>
<ChildA/>
<ChildB/>
</React.Fragment>
))
}
)
}
Portals 将子节点渲染到父组件之外的节点上
比如一个有遮罩的模态框,必然需要添加在body上。
const bodyEl = document.getElementsByTags('body')[0]
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
bodyEl.appendChild(this.el);
}
componentWillUnmount() {
bodyEl.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children, // 下面的Modal中的Child
this.el,
);
}
}
// 将模态框使用在父组件中 此时的Modal组件相当于一个中转 使用Portal则挂载在父组件上但可以不受父组件根元素div的限制
// 然后再modal中将child(即模态框的实际内容)添加在创建的div上 再将div(模态框)添加到body上
class Parent extends React.Component {
render() {
return (
<div>
<Modal>
<Child />
</Modal>
</div>
);
}
}
function Child() { // 无状态组件
return (
<div className="modal">
<button>Click</button>
</div>
);
}
高阶组件
高阶组件就是一个函数,且该函数接受一个组件作为参数,然后返回一个新的组件。相当于是一个工厂用来增加组件的,a,b组件都需要一把锤子,就将其传值给锤子工厂(高阶组件)去给a,b组件装一把锤子,这里的锤子就是公共部分,a,b都需要用到的。
高阶组件的参数:需要混入公共功能的组件
高阶组件的返回:已经增强过的组件
class Acom extends React.Component {
render() {
return (
<div>
<p>我是a,我需要一把{this.props.msg}</p>
<button onClick={this.props.show}>展示锤子吧!!!</button>
</div>
);
}
}
class Bcom extends React.Component {
render() {
return (
<div>
<p>我是b,我也需要一把{this.props.msg}</p>
<button onClick={this.props.show}>展示锤子吧!!!</button>
</div>
);
}
}
function myHighCom(Com){
// 匿名类 相当于匿名函数
return class extends React.Component {
constrcutor(props){
super(props)
this.state = {
wuqi:"锤子"
}
}
render(){
return <Com wuqi={this.state.wuqi} show={this.show}></Com>
}
show(){
console.log('展示我的锤子')
}
componentDidMount(){
console.log('我们在生命周期钩子函数里面也要做同样的事情')
}
}
}
const AplusCom = myHighCom(Acom)
const BplusCom = myHighCom(Bcom)
// 使用增强过的组件
class Parent extends React.Component {
render() {
return (
<div>
<AplusCom/>
<hr/>
<BplusCom/>
</div>
);
}
}
ReactDOM.render(<Parent />,document.getElementById('app'))
网友评论