React入门实践(篇幅较长,持续更新)

199 阅读25分钟

基本引入

此项目React版本为 V17.0.2

全局安装react脚手架

npm install -g create-react-app
create-react-app reactDemo   //用脚手架创建React项目

src/index.js

// 下面这两个包是react必要的两个包
import React from 'react'
import ReactDOM from 'react-dom'
// app是一个入口文件
import APP from './APP'
// 利用ReactDOM的render将APP渲染到public/index.html上  这个root就是在index.html中
ReactDOM.render(<APP />,document.getElementById('root'))

public/index.html

image.png src/APP.js

import React,{Component} from 'react'  

class APP extends Component {
    render(){
        return (
            <div>
                欢迎访问{false?'SBB':"ReactDemoaa"}项目   
            </div>
        )
    }
}
export default APP

APP里使用了{false?'SBB':"ReactDemoaa"}来将表达式注入到html中这就是React中的JSX
APP这个组件里面使用render函数将JSX渲染html中,这和vue一样在render中的顶层只能有一个html元素(要是你实在是不想加这个顶层html容器元素的话,那么可以使用Fragment,当然你也可以使用React的短语法<></>

import React,{Component,Fragment} from 'react'

class APP extends Component {
    render(){
        return (
            <Fragment>
                欢迎访问{false?'SBB':"ReactDemoaa"}项目
                欢迎访问{false?'SBB':"ReactDemoaa"}项目    
            </Fragment>
        )
    }
}
export default APP

JSX

JSX语法是JavaScript的语法扩展,是一种在JS代码中书写HTML的语法形式,与传统的JS语法相比,它具有

  • 更好的性能,React通过JSX生成virtual DOM,渲染到真实的HTML上
  • 更舒适的写法,React帮助我们去操作DOM,我们只需要关注业务逻辑,不需要频繁的书写getElemntById()等等操作DOM的方法。

渲染数据到html中

APP.js

import React,{Component,Fragment} from 'react'
class APP extends Component{
    constructor(props){
        super(props);  // 调用父级组件 Component的constructor方法
        this.state= {
            value:'asasa',
           
        }
    }
    render(){
        return (
            <Fragment>      
                <p>这是{this.state.value}</p>
                
            </Fragment>
        )
    }
    
}
export default APP

响应式数据

import React,{Component,Fragment} from 'react'
class APP extends Component{
    constructor(props){
        super(props);
        this.state= {
            value:'',
           
        }
      
    }
    render(){
        return (
            <Fragment>      
                <p>这是{this.state.value}</p>
                <input value={this.state.value} onChange={(e)=>this.inputChange(e,'aaa')}></input>
              
            </Fragment>
        )
    }
    inputChange=(e,val)=>{
        console.log('传递的参数是:',val);
        console.log('触发了事件:',e.target.value);
        // this.state.value = e.target.value
        this.setState({
            value:e.target.value
        })
    }
}
export default APP

四个知识点:

  • 需要进行单向数据绑定的数据在APP类中只能存储在this.state中(这就如同在Vue里,this.data中的数据全被vue执行了双向数据绑定一样,当我们存在某个数据不需要去给vue进行双向数据绑定收集依赖时,我就无需在this.data.return()中定义)
  • 对于函数的参数传递,我们可以使用onClick={(e)=>this.addItem(e,value)}的形式,e指的是原生事件对象(这里可使用ref形式去获得e.target的值),value就是我们传入的参数了
  • 这里不使用箭头函数的话,那我们还可以使用bind修改this的指向
  • 对于在this.state中存储的数据,我们使用this.setState去修改 从这里我们可以看出React是单向数据流

this.setState

  1. this.setState是异步的
  2. this.setState的变化只会发生一次(同一个数据变更DOM只会更新一次)
  3. this.setState的第一个参数可以是一个function,参数是上一个state和props
  4. this.setState的第二个参数是this.setState异步操作完成后的回调 看下面的例子
import React from 'react';
interface IProps {

}
interface IState {

  count:number
}
class App extends React.Component<IProps,IState> {
  constructor(props){
    super(props)
    this.state = {
      
      count:0
    }
  }

  render(){
      return (
            <div>
              <p onClick={()=>{
                this.setState({count:this.state.count+1})
                console.log("当前的state是:",this.state.count)
              }}>点击</p>
              <p>{this.state.count}</p>
            </div>

      )
  }
}

export default App;

当我们点击按钮的时候,发现console.log没有输出count正确的结果。因为 this.setState是异步的。
那我们应该怎么做,才能输出当前正确的count
通过this.setState的第二个参数
下面我们改一下这个代码:

import React from 'react';
interface IProps {

}
interface IState {

  count:number
}
class App extends React.Component<IProps,IState> {
  constructor(props){
    super(props)
    this.state = {
      
      count:0
    }
  }

  render(){
      return (
            <div>
              <p onClick={()=>{
                this.setState({count:this.state.count+1},()=>{
                  console.log("当前的state是:",this.state.count)
                })
              }}>点击</p>
              <p>{this.state.count}</p>
            </div>

      )
  }
}

export default App;

Vue的异步更新队列一样,**react在面对一个数据变化的过程中,只会更新一次DOM

import React from 'react';
interface IProps {

}
interface IState {

  count:number
}
class App extends React.Component<IProps,IState> {
  constructor(props){
    super(props)
    this.state = {
      
      count:0
    }
  }

  render(){
      return (
            <div>
              <p onClick={()=>{
                this.setState({count:this.state.count+1},()=>{
                  console.log("当前的state是:",this.state.count)
                })
                this.setState({count:this.state.count+1},()=>{
                  console.log("当前的state是:",this.state.count)
                })
              }}>点击</p>
              <p>{this.state.count}</p>
            </div>

      )
  }
}

export default App;

我们可以看到,当我们点击了一次,count并没有+2,而是+1,那是因为react在DOM没更新之前,获取到的count只是1。
那么我们应该怎么样,实现这个点击一次调用两次setState的操作呢?
答案是 通过setState的第一个参数,我们可以传入一个函数

import React from 'react';
interface IProps {

}
interface IState {

  count:number
}
class App extends React.Component<IProps,IState> {
  constructor(props){
    super(props)
    this.state = {
      
      count:0
    }
  }

  render(){
      return (
            <div>
              <p onClick={()=>{
                this.setState((preState,preProps)=>{
                  return {
                    count:preState.count+1
                  }
                },()=>{
                  console.log("当前的state是:",this.state.count)
                })
                this.setState((preState,preProps)=>{
                  return {
                    count:preState.count+1
                  }
                },()=>{
                  console.log("当前的state是:",this.state.count)
                })
              }}>点击</p>
              <p>{this.state.count}</p>
            </div>

      )
  }
}

export default App;

从上面我们可以看出,this.setState的第一个参数也可以是一个函数,函数的第一个参数是:上一个state,函数的第二个参数是:上一个props

循环列表渲染

import React,{Component,Fragment} from 'react'
class APP extends Component{
    constructor(props){
        super(props);
        this.state= { 
            value:'asasa',
            list:['q','2','2','e']
        }
    }
    render(){
        return (
            <Fragment>      
              <div>{this.state.list.map(item=>{
                  if(item==2){
                   
                  }
                  return <p>{item}</p>
              })}</div>
            </Fragment>
        )
    }
}
export default APP
  • 我们使用this.state.list.map函数去进列表渲染
  • 在循环函数map中return出了一个html
  • 我们在map中还进行了一个条件渲染,只渲染非'2'的元素 在看一个例子
import React,{Component,Fragment} from 'react'
class APP extends Component{
    constructor(props){
        super(props);
        this.state= { 
            value:'asasa',
            list:['q','2','2','e']
        }
        this.addItem = this.addItem.bind(this)
    }
    render(){
        return (
            <Fragment>
              <p>{this.state.value}</p>
              <input onChange={this.change}></input>
              <div onClick={this.addItem}>添加子项</div>      
              <div>{this.state.list.map(item=>{
                  return <p>{item}</p>
              })}</div>
            </Fragment>
        )
    }
    addItem(){
        this.setState({
            list:[...this.state.list,this.state.value]
        })
    }
    change = (e)=>{  
        console.log('触发了change函数');
        this.setState({
            value:e.target.value
        })
    }
}
export default APP

如同Vue中的v-for一样,我们需要对渲染的列表添加一个key(React和Vue中的Key作用都是一样的,都是根据这个key去进行diff算法比对),看下面的例子

import React,{Component,Fragment} from 'react'
class APP extends Component{
    constructor(props){
        super(props);
        this.change = this.change.bind(this)
        this.addItem = this.addItem.bind(this)
        this.state= { 
            value:'asasa',
            list:[
                {
                    value:'q',
                    id:1
                },
                {
                    value:'w',
                    id:2
                },
                {
                    value:'e',
                    id:3
                },
            ]
        }
    }
    render(){
        return (
            <Fragment>
              <p>{this.state.value}</p>
              <input onChange={this.change}></input>
              <div onClick={this.addItem=}>添加子项</div>      
              <div>{this.state.list.map((item,index)=>{
                  return <p key={item.id}>{item.value}</p>
              })}</div>
            </Fragment>
        )
    }
    addItem(){
        this.setState({
            list:[...this.state.list,this.state.value]
        })
    }
    change(e){
        console.log('触发了change函数');
        this.setState({
            value:e.target.value
        })
    }
}
export default APP

删除this.state.list中的某个元素

import React,{Component,Fragment} from 'react'
class APP extends Component{
    constructor(props){
        super(props);
        this.change = this.change.bind(this)
        this.addItem = this.addItem.bind(this)
        this.state= { 
            value:'asasa',
            list:[
                {
                    value:'q',
                    id:1
                },
                {
                    value:'w',
                    id:2
                },
                {
                    value:'e',
                    id:3
                },
            ]
        }
    }
    render(){
        return (
            <Fragment>
              <p>{this.state.value}</p>
              <input onChange={this.change}></input>
              <div onClick={this.addItem}>添加子项</div>      
              <div>{this.state.list.map((item,index)=>{
                  return <p key={item.id} onClick={this.delItem.bind(this,index)}>{item.value}</p>
              })}</div>
            </Fragment>
        )
    }
    addItem(){
        this.setState({
            list:[...this.state.list,this.state.value]
        })
    }
    delItem(index){
        const list = this.state.list;
        list.splice(index,1);
        this.setState({
            list:list
        })
    }
    change(e){
        console.log('触发了change函数');
        this.setState({
            value:e.target.value
        })
    }
}
export default APP
  • 我们使用delItem删除了下标为index的list元素,这里我们还是使用的this.setState去修改this.state中的元素

添加CSS样式

新建一个src/ndex.css

.p-c{
    color: brown;
}

在APP.js中引入

import React,{Component,Fragment} from 'react'
import './index.css'
class APP extends Component{
    constructor(props){
        super(props);
        this.change = this.change.bind(this)
        this.state= { 
            value:'asasa',
            list:[
                {
                    value:'q',
                    id:1
                },
                {
                    value:'w',
                    id:2
                },
                {
                    value:'e',
                    id:3
                },
            ]
        }
    }
    render(){
        return (
            <Fragment>
                {/* 这是注释 */}
              <p className={this.state.value==1?'p-c':''}>{this.state.value}</p>
              <input onChange={this.change}></input>
              
            </Fragment>
        )
    }
    change(e){
        console.log('触发了change函数');
        this.setState({
            value:e.target.value
        })
    }
}
export default APP
  • 我们在render中的html中,使用className去给html元素设置样式
  • 在React的JSX语法中,我们在className中添加了三元运算符,当this.state.value===1时,添加p-r,否则就什么也不添加

添加行内style样式

import React,{Component,Fragment} from 'react'
import './index.css'
class APP extends Component{
    constructor(props){
        super(props);
        this.change = this.change.bind(this)
        this.addItem = this.addItem.bind(this)
        this.state= { 
            value:'asasa',
            list:[
                {
                    value:'q',
                    id:1
                },
                {
                    value:'w',
                    id:2
                },
                {
                    value:'e',
                    id:3
                },
            ]
        }
    }
    render(){
        return (
            <Fragment>
                {/* 这是注释 */}
                 {/* 我们给这个P标签加了一个行内样式 */}
              <p style={{fontSize:20px}}>{this.state.value}</p>
              <input onChange={this.change}></input>
              <div onClick={this.addItem}>添加子项</div>      
              <div>{this.state.list.map((item,index)=>{
                  return <p className="p-c" key={item.id} onClick={this.delItem.bind(this,index)}>{item.value}</p>
              })}</div>
            </Fragment>
        )
    }
    addItem(){
        this.setState({
            list:[...this.state.list,this.state.value]
        })
    }
    delItem(index){
        const list = this.state.list;
        list.splice(index,1);
        this.setState({
            list:list
        })
    }
    change(e){
        console.log('触发了change函数');
        this.setState({
            value:e.target.value
        })
    }
}
export default APP
  • 从上我们可以看到,我们给p元素添加了一个行内样式,p的style我们传入了一个对象
  • 在React中,我们给元素添加的style是一个对象,这个对象中的css是小驼峰的写法

组件化开发

如同Vue一样,React也推崇组件化开发 我们创建一个menu.jsx(.js后缀也行的)

import React, { Component } from 'react';
class Menu extends Component {
    state = { 
        list:[
            {
                value:'栏目1',
                id:1
            },
            {
                value:'栏目2',
                id:2
            },
            {
                value:'栏目3',
                id:3
            },
        ]
     }
    render() { 
        return (  
            <div>
                {
                    this.state.list.map(item=>{
                       return  <p key={item.id}>{item.value}</p>
                    })
                }
            </div>
        );
    }
}
 
export default Menu;

在APP.js中引入

import React,{Component,Fragment} from 'react'
import './index.css'
import Menu from './menu'
class APP extends Component{
    constructor(props){
        super(props);
        this.change = this.change.bind(this)
        this.addItem = this.addItem.bind(this)
        this.state= { 
            value:'asasa',
            list:[
                {
                    value:'q',
                    id:1
                },
                {
                    value:'w',
                    id:2
                },
                {
                    value:'e',
                    id:3
                },
            ]
        }
    }
    render(){
        return (
            
            <Fragment>
              <p>{this.state.value}</p>
              <input onChange={this.change}></input>
              <div onClick={this.addItem}>添加子项</div>      
              {/* <div>{this.state.list.map((item,index)=>{
                  return <p className="p-c" key={item.id} onClick={this.delItem.bind(this,index)}>{item.value}</p>
              })}</div> */}
              <div><Menu ></Menu></div>
            </Fragment>
        )
    }
    addItem(){
        this.setState({
            list:[...this.state.list,this.state.value]
        })
    }
    delItem(index){
        const list = this.state.list;
        list.splice(index,1);
        this.setState({
            list:list
        })
    }
    change(e){
        console.log('触发了change函数');
        this.setState({
            value:e.target.value
        })
    }
}
export default APP
  • 这里有一个小细节,我们在顶层Fragment中是不可以直接使用Menu的,需要包裹一层容器才行

父子组件交互

这里通过一个完整的例子演示了几种情况

  • 父传子---通过props传递
  • 子传父---通过触发父组件的函数 APP.js
import React,{Component,Fragment} from 'react'
import './index.css'
import Menu from './menu'
class APP extends Component{
    constructor(props){
        super(props);
        this.delItem = this.delItem.bind(this);
        this.change = this.change.bind(this);
        this.addItem = this.addItem.bind(this);
        this.state= {
            tempValue:null,
            inputDOM:null, 
            value:'asasa',
        }
    }
    render(){
        return (
            <Fragment>
              <p>{this.state.value}</p>
              {/* 
                 这里的input使用了ref 代替原生的e.target
              */}
              <input ref={(inputDOM)=>this.inputDOM=inputDOM} onChange={this.change}></input>   
              <div onClick={this.addItem}>添加子项</div>      
              <div><Menu  content="父组件传入的值" addItem={this.state.tempValue} delItem={this.delItem}></Menu></div>
            </Fragment>
        )
    }
    addItem(){
        this.setState({
            tempValue:this.state.value
        })
    }
    delItem(value){
        console.log('触发了父组件函数',value);
        this.setState({
            value:""
        })
    }
    change(e){
        console.log('触发了change函数');
        this.setState({
            value:this.inputDOM.value   // 我们这里使用了ref对象取代了原生的e.target
        })
    }
}
export default APP

menu.js

import React, { Component } from 'react';
import propTypes from 'prop-types'   // 对传入的props进行校验 跟Vue的props差不多
class Menu extends Component {
    state = { 
        list:[
            {
                value:'栏目1',
                id:1
            },
            {
                value:'栏目2',
                id:2
            },
            {
                value:'栏目3',
                id:3
            },
        ]
     }
     constructor(props){
         super(props)
         this.delFatherItem = this.delFatherItem.bind(this);
     }
    render() { 
        return (  
            <div>
                {
                    this.state.list.map(item=>{
                       return  <p key={item.id}>{item.value}</p>
                    })
                }
                <p>这是:{this.props.content}</p>
                <p>这是:{this.props.addItem}</p>
                <div onClick={this.delFatherItem}>点击这里删除父组件的值</div>
            </div>
        );
    }
    delFatherItem(){
        console.log('点击了删除。。。');
        this.props.delItem(this.state.list[0]);
    }
}
Menu.propTypes = {
    content:propTypes.string.isRequire,   // 规定content只能为string类型且为必传
    delItem:propTypes.func,     //规定delItem只能为function类型
    addItem:propTypes.string
}
 
export default Menu;

从上面我们可以看出

  • 父传子可通过props
  • 子传父可通过props获取父组件的方法,之后触发父组件的方法
  • 增加ref绑定(如Vue中的ref)能获取这个DOM对象
  • 对props我们可以增加import propTypes from 'prop-types'校验(就像Vue中的props校验一样)

插槽

不具名插槽

如同Vue中的插槽一样,React里也有插槽(只能说是类似插槽,React更喜欢将组件间的数据交互用props去表示)

这种方法可能使你想起别的库中“槽”(slot)的概念,但在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递 父组件往子组件标签内插入html,子组件通过{this.props.children}渲染
此时的插槽作用域是父组件的作用域,看下面的例子 APP.js

    import React,{Component,Fragment} from 'react'


    import Menu from './menu'

    class APP extends Component{
        constructor(props){   //组件生成
           
            super(props);
           
            this.state= { 
                value:'asasa',
            }
        }


        render(){   // 组件挂载中  当state、props发生变化就会执行
            console.log('APP组件正在渲染。。');
            return (
                <Fragment>
                <p>{this.state.value}</p>
               <Menu content="父组件的值"   >
                    <h1>这是父组件的{this.state.value}</h1>
               </Menu></div>
            
                </Fragment>
            )
        }
       
    }
    export default APP

menu.js

import React, { Component } from 'react';
import propTypes from 'prop-types'
class Menu extends Component {
    state = { 
       
     }
     constructor(props){
         super(props)
       
     }
    //  shouldComponentUpdate 函数可让子组件是否会渲染  return Boolean
    // 参数:newProps---新的更改的props  newState 新的更改的state
     shouldComponentUpdate(newProps,newState){
         if(newProps.children!== this.props.children||newProps.content!==this.props.content){
             return true
         }else {
             return false
         }
     }
    render() { 
        console.log('menu组件的渲染。。。') 
        return ( 
            <div>
                <p>这是:{this.props.content}</p>
                {this.props.children}
            </div>
        );
    }

}
Menu.propTypes = {
    content:propTypes.string.isRequired,
}
 
export default Menu;

我们在APP组件内的Menu组件中插入一段JSX,这段JSX中输出了APP组件的this.state.value

具名插槽

既然有匿名插槽,那就代表有具名插槽。
其实具名插槽就跟props一样,就是使用props传入一个数据,子组件渲染而已
app.js

import React,{Component} from 'react'
import Children from "./children"
import "./App.css"
class APP extends Component{
  constructor(props){
    super(props)
    this.state = {
      value:1,
      msg:"我是app组件的数据"
    }
    this.initBind();
  }
  initBind(){
    this.changeValue = this.changeValue.bind(this)
  }
  render(){ 
    return (
      <div>
        <p>state中的value是:{this.state.value}</p>
        <Children clickFather={this.changeValue} value={this.state.msg} content={<div>具名插槽</div>}>
          <h1>这是slot。。。</h1>
        </Children>
      </div>
    )
  }
  changeValue(val){
    console.log("看看参数:",val)
    this.setState({
      value:this.state.value+1
    })
  }

}
export default APP

children.js

const Children = (props)=>{
    return (
        <>
            <div>我是father组件</div>
            <div>{props.value}</div>
            <p onClick={()=>props.clickFather(123456)}>点击</p>
            {props.children}
            {props.content}
        </>
    )
}

export default Children

我们通过props.content传入一段jsx,这就是具名插槽

生命周期

image.png 官网链接:projects.wojtekmaj.pl/react-lifec… 相关内容在官网中,此不在赘述。
这里我只会讲解几个比较重要的生命周期函数。

constructor

组件初始化入口,主要作用就是:设置state数据,更改方法的this指向(bind去更改,或者使用箭头函数),只会执行一次

    constructor(props){   //组件生成
        console.log('APP组件即将执行')
        super(props);
        this.delItem = this.delItem.bind(this);
        this.change = this.change.bind(this);
        this.addItem = this.addItem.bind(this);
        this.state= {
            tempValue:null,
            inputDOM:null,  
            value:'asasa',
        }
    }

render

正如我们上面看到的一样,render函数返回一串JSX,React会通过这个生成虚拟DOM,和真实DOM进行diff比对之后更新

shouIdComponentUpdate

这个生命周期用于组件优化
这个生命周期发生在数据更新时(props更新、state更新) 我们看上面的例子,当APP组件中渲染了Menu组件,那么当APP组件的props和setState调用时,render函数就会重新执行,这时候menu组件明明没有数据变化,但是也会被重新渲染了!!!。
因此,我们可以使用shouIdComponentUpdate可以决定子组件Menu是否会被重新渲染。

// Menu组件中
    //  shouldComponentUpdate 函数可让子组件是否会渲染  return Boolean
    // 参数:newProps---新的更改的props  newState 新的更改的state
     shouldComponentUpdate(newProps,newState){
         console.log('newProps.addItem:',newProps.addItem);
         if(newProps.addItem!== this.props.addItem){   // 当props中的addItem没有变化时就不会重新渲染Menu组件
             return true
         }else {
             return false
         }
     }

ComponentDidMount

这个生命周期发生于,组件挂载阶段,此时虚拟DOM已经渲染到HTML(插入到DOM树)中
这个生命周期的作用就是:引入相关的axios依赖(记得要在componentWillUnmount中注销掉相关实例)、当你的state依赖渲染好的DOM元素,那么就需要在这个生命周期操作,但是请注意,会导致render执行两次,且会有性能问题

    /**
     * 组件挂载完成  只触发一次 
     * 适合加入axios等实例对象 ---- 要在componentWillUnmount中删除相关实例对象
     * 如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,你可以使用此方式处理
     */
    import React,{Component,Fragment} from 'react'
    import './index.css'
    import axios from 'axios'

    class APP extends Component{
    async componentDidMount(){  
            console.log('组件挂载完成。。');
            this.setState({    // 由于调用了setState 所以此时的render会渲染两次
                value:'12121'
            })
            try {
                const res = await axios.get('http://localhost:8088/test')  
                console.log('res是:',res);
            } catch (error) {
                console.log('请求失败,错误是:',error);
            }
        }
    }
      export default APP

componentDidUpdate

componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。 这个生命周期最经典的用法就是官网上介绍的,我们可以通过比对,旧值和新值去决定是否要发送Ajax请求

componentDidUpdate(prevProps) {
  // 典型用法(不要忘记比较 props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

componentWillUnmount

componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它

无状态组件

无状态组件(Stateless Functional Component)又称之为函数组件

为什么我们先需要无状态组件?

因为无状态组件相对于类组件更为简单(React也能让无状态组件与类组件完美配合) 举个例子,我们需要渲染循环列表
类组件这样做

import React, { Component } from 'react';
class Menu extends Component {
    constructor(props) {
        super(props);
        this.state = { 
            movies:[{_id:1121,title:'我是title1'},{_id:15451,title:'我是title2'}]
         }

    }
    render() { 
        return (<div>
            
           <ul>
                {
                     this.state.movies.map(i=><li  key={i._id}>{i.title}</li>)
                 }
           </ul>
        </div>  );
    }



    componentDidMount(){
      
    }
}
 
export default Menu;

无状态组件是这么做的


const Menu = (props)=>{
    console.log('看看props',props);
    return (<div>
            
        <ul>
             {
                  props.value.map(i=><li key={i._id}>{i.title}</li>)
              }
        </ul>
     </div>  );
}
 
export default Menu;

在调用无状态组件的父级组件:

 <NavBar value={[{_id:1121,title:'我是title1'},{_id:15451,title:'我是title2'}]}></NavBar>

两个地方需要注意 1.无状态组件没有自己的state,所以我们只能使用props,由父组件替我们暂时保存值 2.无状态组件没有自己的生命周期

无状态组件传值

默认,子组件是无状态组件

父传子 ------ 通过props进行传值

app.js

import React,{Component,Fragment} from 'react'
import Children from "./Children"
import "./App.css"
class APP extends Component{
  constructor(props){
    super(props)
    this.state = {
      value:1,
      msg:"我是app组件的数据"
    }
    this.initBind();
  }
  initBind(){
    
  }
  render(){ 
    return (
      <div>
        <p>state中的value是:{this.state.value}</p>
        <Children value={this.state.msg}></Children>
      </div>
    )
  }

}
export default APP

Children.js

const Children = (props)=>{
    return (
        <>
            <div>我是father组件</div>
            <div>{props.value}</div>

            <p onClick={()=>props.clickFather(123456)}>点击</p>
        </>
    )
}

export default Children

从上面我们可以看到,我们使用props往无状态组件中传入了父组件的数据

子传父 ----- 通过调用父的函数

app.js

import React,{Component,Fragment} from 'react'
import Children from "./children"
import "./App.css"
class APP extends Component{
  constructor(props){
    super(props)
    this.state = {
      value:1,
      msg:"我是app组件的数据"
    }
    this.initBind();
  }
  initBind(){
    this.changeValue = this.changeValue.bind(this)
  }
  render(){ 
    return (
      <div>
        <p>state中的value是:{this.state.value}</p>
        <Children clickFather={this.changeValue} value={this.state.msg}></Children>
      </div>
    )
  }
  changeValue(val){
    console.log("看看参数:",val)
    this.setState({
      value:this.state.value+1
    })
  }

}
export default APP

children.js

const Children = (props)=>{
    return (
        <>
            <div>我是father组件</div>
            <div>{props.value}</div>

            <p onClick={()=>props.clickFather(123456)}>点击</p>
        </>
    )
}

export default Children

当我们点击按钮时,发现子组件的数据通过函数clickFather传递到了父组件

祖父组件传值

使用React提供的ProviderConsumer可实现:

zh-hans.reactjs.org/docs/contex… context.js

import React from "react"
const {Provider,Consumer} = React.createContext(null);
export  {
    Provider,
    Consumer
}

Father.jsx

import React,{useState} from 'react'
import Son from "./Son"
import {Provider} from "./context"
const Father = (props)=>{
    const [info,setInfo] = useState("Fahter info")
    return (
        <>
          <p>{info}</p>
          <Provider value="Fathern provider info">
            <Son></Son>
          </Provider>
        </>
    )
}
export default Father

Son.jsx

import React,{useState} from 'react'
import GrandSon from './GrandSon'
import {Consumer} from './context'
const Son = (props)=>{
    const [info,setInfo] = useState("Son info")
    return (
        <>
        <p>{info}</p>
        <Consumer>
            {
                (val)=>{
                    return (
                        <div>
                            <p> 我是Son中的信息:{val}</p>
                            <GrandSon></GrandSon>
                        </div>
                    )
                }
            }
        </Consumer>
            
        </>
    )
}
export default Son

GrandSon.jsx

import React,{useState} from 'react'
import {Consumer} from "./context"
const GrandSon = (props)=>{
    const [info,setInfo] = useState("GrandSon info")
    return (
        <>
         <p>{info}</p>
        <Consumer>
            {
                (val)=>(
                    <div>
                        我是GrandSon中的信息:{val}
                    </div>
                )
            }
        </Consumer>
       
        </>
    )
}
export default GrandSon

react-router

react中有三个包能够让我们管理路由

  • react-router:包含最简单的router功能,一般不使用
  • react-router-native:在react-native的应用中会用到
  • react-router-dom:浏览器端一般使用这个 安装:
npm install react-router-dom

我这里安装的是v6.2.1版本的
在APP中编写路由相关处理逻辑:

import React, { useState } from 'react'
import Father from './components/Father';
import Son from "./components/Son"
import GrandSon from "./components/GrandSon"
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const APP = ({})=>{
  const [info,setInfo] = useState('init APP');
  return (
    <div>
      {info}
     
        <Routes>
          <Route path='/Father' element={<Father />} />
          <Route path='/Son' element={<Son />} />
          <Route path='/GrandSon' element={<GrandSon />} />
        </Routes>
     
    </div>
  )
}
export default APP

index.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {BrowserRouter} from 'react-router-dom'
// React.StrictMode 代表的是 react 的 严格模式 帮助我们检测过时的API,语法问题等
ReactDOM.render(
  <React.StrictMode>   
    <BrowserRouter>    //  使用html5 的路由模式 也可换成vue的hans模式:HashRouter
      <App name="React"/>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

  • 我们可以使用useSearchParams()钩子来获取/Father?name=arzhu中的?后的值
  • 我们可以使用useNavigate()钩子进行编程式路由跳转
  • 我们可以使用useParams()钩子获取/Father/123中的123,在路由中需要配置为:<Route path='/Father/:id' element={<Father />} /> 更多内容请参考

官方文档:react-guide.github.io/react-route…
zhuanlan.zhihu.com/p/431389907

HOC组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式

具体可看:juejin.cn/post/687250… 主要的作用是
1.复用组件逻辑
2.代理操作

主要的实现方式是:属性代理反向继承

属性代理

我们使用一个函数,return出一个组件A,函数的参数就是需要被HOC的组件A,我们在这个函数中可以进行许多操作:

  1. 操作props,添加公共属性
  2. 控制渲染
  3. 复用公共逻辑(操作state,添加生命周期逻辑等)
操作props,添加公共属性

新建withHOCson.jsx

import React,{useState} from 'react';
const withHOCson = (WrappedComponent)=>{
    return class extends React.Component {  // 返回了一个匿名函数
        render() {
            const newProps = {...this.props,type:456};
            return (  // 匿名函数渲染了 参数组件
                <>
                    <WrappedComponent {...newProps}></WrappedComponent>
                </>
            )
        }
    }
}
export default   withHOCson

新建Son.jsx

import React,{useState} from 'react'
import withHOCson from "./withHOCson"

const Son = (props)=>{
    return (
        <>
                 <div>props中的id:{props.id}</div>
                 <div>SON INFO</div>  
                 
        </>
    )
}
const ExplameCom = withHOCson(Son)
export default ExplameCom

app.jsx

      <ExplameCom id={123}></ExplameCom>

从上面我们可以看到,在withHOCson中,我们返回了一个组件,并且,将props注入了一个共有属性type
当然了,上面的Son.jsx换成class类组件也是一样的:

import React,{useState} from 'react'
import withHOCson from "./withHOCson"
class Son extends React.Component {
    render () {
        console.log(this.props);
        return (
            <>
                <p>props:{this.props.id}</p>
                <div>SON INFO</div>
            </>
        )
      }
}
const ExplameCom = withHOCson(Son)
export default ExplameCom
控制渲染

withHOCson中,我们修改如下,其余不变

import React,{useState} from 'react';
const withHOCson = (WrappedComponent)=>{
    return class extends React.Component {
        render() {
            const newProps = {...this.props,type:456};
            return (
                <>
                    <div>我是HOC的信息</div>
                    <WrappedComponent {...newProps}></WrappedComponent>
                </>
            )
        }
    }
}
export default   withHOCson

我们在withHOCson中写入了一段jsx,这就算是在HOC中渲染的共有jsx部分,当然了我们也可以通过props传入一个flag,控制某些组件jsx隐藏或显示 修改withHOCson:

import React,{useState} from 'react';
const withHOCson = (WrappedComponent)=>{
    return class extends React.Component {
        render() {
            const newProps = {...this.props,type:456};
            return (
                <>
                    {this.props.flag===true&&<div>我是HOC的信息</div>}
                    <WrappedComponent {...newProps}></WrappedComponent>
                </>
            )
        }
    }
}
export default   withHOCson

修改app.jsx:

  <ExplameCom id={123} flag={true}></ExplameCom>

可以看到,本质上,HOC跟我们处理别的组件也是类似的,但是不同的是,我们通过使用HOC能达到,复用逻辑的作用

复用公共逻辑

这里我们举一个受控组件的例子,受控组件指的是那些,组件状态由用户的操作决定的组件,比如input,select等等
场景:我们需要使用input做一个双向数据绑定的组件,此外,还需要输入一些额外的信息,我们可以想到,使用组件props很容易实现,但是我们另一个组件也需要实现这个需求,难道我们要将代码C+V么?显然是不正确的,于是我们考虑使用HOC去实现
withHOCson.jsx

import React,{useState} from 'react';
const withHOCson = (WrappedComponent)=>{
    return (props)=>{   // 这里我使用了 hooks 的写法 
        const [value,setVale] = useState(props.originVal)
        const change = (e)=>{
            setVale(e.target.value);
        }
        const newProps = {
            ...props,
            type:456,
            value:value,
            change:change

        };
        return (
            <>
             <WrappedComponent {...newProps}></WrappedComponent>
             </>
        )
    }
}
export default   withHOCson

Son.jsx

import React, { useState } from 'react'
import withHOCson from "./withHOCson"
const Son = (props) => {
    return (
        <>
            <p>props的id:{props.id}</p>
            <p>props的value:{props.value}</p>
            <div>SONC INFO</div>
            <input value={props.value} onChange={props.change}></input>
        </>
    )
}
const ExplameCom = withHOCson(Son)
export default ExplameCom

Sonc.jsx

import React, { useState } from 'react'
import withHOCson from "./withHOCson"
const Sonc = (props) => {
    return (
        <>
            <p>props的id:{props.id}</p>
            <p>props的value:{props.value}</p>
            <div>SONC INFO</div>
            <input value={props.value} onChange={props.change}></input>
        </>
    )
}
const ExplameComc = withHOCson(Sonc)
export default ExplameComc

app.jsx

      <ExplameCom id={123} originVal={"我是ExplameCom组件"}></ExplameCom>
      <ExplameComc id={456} originVal={"我是ExplameComc组件"}></ExplameComc>

我们在withHOCson中根据传入的props.orginVal作为input的value初始值,在input的onCahnge事件中调用了HOC中定义的propschange事件,这个事件内部调用了setVale更新value的值
我们对两个组件SonSonc中的双向绑定功能,进行了逻辑复用

反向继承

反向继承跟属性代理的思路不同,反向继承是在HOC中返回一个继承了HOC参数组件的类组件,这么一说很拗口难懂,所以直接看例子:
Sonc.jsx

import React, { useState } from 'react'
import withReverse from "./withReverse"
class Sonc extends React.Component {
    constructor(){
        super()
        this.state = {
            msg:'这是 Sonc 的信息'
        }
    }
    render(){
        return (
            <>
                <p>SONC INIT</p>
            </>
        )
    }
}
const ExplameComc = withReverse(Sonc)
export default ExplameComc

withReverse.jsx

import React from 'react'

const withReverse = (WrappedComponent)=>{
    return class extends WrappedComponent{
        render (){
            return super.render();  // 咱们直接通过 super.render()调用父类的render
        }
    }
}
export default withReverse

app.jsx

<ExplameComc></ExplameComc>

从上面我们可以看到,反向继承的定义是 函数中返回一个继承了函数参数的匿名组件
反向继承还可以操作父类组件中的state
withReverse.jsx

import React from 'react'

const withReverse = (WrappedComponent)=>{
    return class extends WrappedComponent{
        constructor(props){
            super(props);
        }
        render (){
            return (
                <>
                <p>HOC中的信息:{this.state.msg}</p>
                <p onClick={()=>this.change()}>点我改变msg</p>
                {/* 渲染 父类的render */}
                {super.render()}  
                </>
            );
        }
        change(){
            this.setState({
                msg:'HOC msg'
            })
        }
    }
}
export default withReverse

Sonc.jsx

import React, { useState } from 'react';
import withReverse from "./withReverse"
class Sonc extends React.Component {
    constructor(){
        super()
        this.state = {
            msg:'sonc state msg'
        }
    }
    render(){
        return (
            <>
                <p>Sonc中的信息:{this.state.msg}</p>
                <p>SONC INIT</p>
            </>
        )
    }
}
const ExplameComc = withReverse(Sonc)
export default ExplameComc

app.jsx不变
当我们点击点我改变msg这个dom的时候,我们发现SoncwithReverse中的msg都发生了改变!!! 这就证明SoncwithReverse共用了一个state 这是一件很危险的事情,这会让数据变更来源不明,不符合react的单项数据流定义
反向继承的主要作用有:

  1. 操作state
  2. 渲染劫持 第一个作用在我们上面的例子已经演示过了。
渲染劫持

何谓渲染劫持?简单的回答就是

修改目标组件的渲染功能

我们之前使用属性代理实现的控制渲染也是属于渲染劫持的一种
再回顾一下我们使用反向继承的原理:一个HOC中返回了一个继承了参数组件的匿名组件,内部可以使用this访问组件的state,生命周期等
那么我们在这个HOC中也可以对参数组件做渲染劫持,渲染出我们希望渲染的数据
render函数的底层实际上是调用了React.createElement()产生的新的React元素,我们做渲染劫持,其实就是去操作这个React元素
withReverse

import React from 'react'

const withReverse = (WrapComponent)=>{
    return class extends WrapComponent {
        componentDidMount () {
          setTimeout(() => {
            console.log(this.props)
          }, 2000)
        }
        render () {
          let testRender = super.render();  
          console.log(testRender)   // 打印看看 render函数返回的对象
          let newProps = { value: '渲染劫持了!!!' }
          let allProps = Object.assign({}, testRender.props, newProps);  //  创建一个新的props
          let finalRender = React.cloneElement(
            testRender,
            allProps,
            testRender.props.children
          );
          return finalRender
        }
      }
}
export default withReverse

Sonc.jsx

import withReverse from "./withReverse"
import React from "react"
class TestComponent extends React.Component {
    constructor(props){
        super(props);
        this.state= {
            msg:'Sonc Init',
            val:1111
        }
    }
    render () {
      return (
          <>
            <input value={this.state.msg} readOnly={true}></input>
          </>
      )
    }
  }
export default withReverse(TestComponent)

app.jsx

<ExplameComc></ExplameComc>

withReverse中我们使用了一个APIReact.cloneElement

以 element 元素为样板克隆并返回新的 React 元素。config 中应包含新的 props,key 或 ref。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,如果在 config 中未出现 key 或 ref,那么原始元素的 key 和 ref 将被保留

可以看到我们渲染劫持了Sonc的数据
HOC组件帮我们实现了React中的逻辑复用,但是也会产生一些让人头疼的问题

如何使用HOC

1、单个使用

当我们只需要一个HOC时,就可以像之上面的例子一样,直接调用export default Explate = withHOC(Test)

2、多个使用

当我们使用多个HOC时,那我们应该如何调用呢?相信你第一个想到的就是直接调用,简单粗暴:

export default Explate = withHOC1(withHOC2(withHOC3(Test)))

但是这种方式太傻了点,也不够骚。
这里介绍一个compose函数,它的作用是,按照参数的传入方式,依次调用

 const compose = (...fns) => (...args) => fns.reduce((val, fn) => fn.apply(null, [].concat(val)), args);

那么我们就可以这么使用了:

const Example = compose(HOC1,HOC2,HOC3)(TestComponent)

约定

使用HOC的时候,为了让我们的代码结构更清晰,BUG数量更少,我们应该遵循下面的约定

  1. 透传props :在HOC中应该透传与自己无关的props
  2. 避免直接修改state:在使用HOC的反向继承实现时,我们可以轻易的去更改state,但是!!!千万不要那么做,这么做会让我们写的组件让人难以理解和调试
  3. 不要在render()中创建HOC:原因是于React的diff有关(感兴趣的可自行了解)
  4. 拷贝组件的静态方法:因为HOC返回的是一个全新的组件,所以经过HOC包装的组件将没有之前定义的静态方法,我们可以使用hoist-non-react-statics拷贝所有静态方法
  5. 使用displayName:我们一般使用React Developer Tools调试React,但是当我们使用了HOC之后就会难以调试,因此我们可以给HOC添加一个displayName属性,代表当前的HOC

缺点

  1. 当我们组件调用大量的HOC的时候,会形成一种HOC嵌套地狱的困境
  2. 由于HOC可以做到渲染劫持修改props,state等的功能,所以会导致我们的代码难以接手

Hook

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。 我个人理解就是在不使用类组件的情况下,在无状态组件中使用类组件的方法和生命周期 看一个官网的例子吧:

count.js

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。它的初始值位为 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
export default Example;

app.js像正常的函数组件那样子去调用

    import Example from './count'
     <Example></Example>

从这个例子我们可以看出

  • 设置一个变量:const [变量名,更改变量的方法(类似setState)] = useState([初始化变量])
  • 我们使用从useState中得到的方法对变量去进行更新。
  • useState就是一个hook钩子

副作用

在先理解副作用之前,我们先理解什么是纯函数
纯函数即:给一个函数相同的参数,这个函数永远返回一样的结果,在react中,我们推崇,函数式编程,这个函数式编程在react中体现的就是一个组件输入相同的props那么我们的逻辑和渲染UI都应该是一致的。
副作用的含义就是,在纯函数中,虽然输入了相同的参数,但是却不一定返回相同的值,这一点在react中表现为,输入相同的props,但是逻辑和UI或许不一致,那么我们这种影响了纯函数输出唯一结果的操作(处理了与返回值无关的操作) 就是副作用
举个例子:

  1. 我们在一个组件中需要发送一个ajax请求,这个ajax的数据是存储在后端的,那么当我们给这个组件输入一个props,我们能保证每次后端返回的数据都是原来的数据么?不能保证吧?所以这就是副作用
  2. 一个组件输入的props,但是我们在组件中使用了console.log等与返回值无关的函数,这也是副作用

Hook钩子

在React的hook中,钩子类型分为

  • React钩子:
  • 自定义钩子:

常用的钩子

useState

这个钩子的作用是能够让我们在无状态组件中使用this.state 基本语法:

const [状态,更改状态的函数] = useState(状态的初始值)

例子:

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。它的初始值位为 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
export default Example;

我们声明了一个变量count和一个更改count的函数setCount,在无状态组件中,我们 不需要使用this 去调用count,我们不再使用 this.setState 去更新count 转而使用 setCount 去更新count

useEffect
  • 取代component­Did­Mountcomponent­Did­Updatecomponent­Will­Unmount
  • 给函数添加副作用
  • React能保证这个钩子在DOM更新之后再执行useEffect的第一个参数的回调
模拟component­Did­Update(伪)

我们这里设计的componentDidUpdate(伪)并不是一个真正意义上的componentDidUpdate,因为我们设计的这个componentDidUpdate(伪)会在组件渲染的时候,初始化一次,真正的componentDidUpdate是不会在组件初始化的时候执行的,后续我们通过useRef+useEffect实现一个真正的componentDidUpdate

useEffect(()=>{
    变更之后的处理(即,触发了componentDidUpdate的处理)
  },[需要追踪变更的状态列表]) 

我们将实现网页的title随着count变更的需求

import React, { useState, useEffect } from 'react';

const App: React.FC = () => {
  const [count, setCount] = useState<number>(0)
  useEffect(()=>{
    document.title=`点击了${count}次`
  },[count])
  return (
    <div >
      
      <button
        onClick={() => {
          setCount(count + 1)
        }}
      >
        Click
      </button>
      <span>count: {count}</span>

    </div>

  )

}

export default App;

这个例子中useEffect传入了两个参数,第二个参数是一个数组,这个数组中存放了会被监听的状态,当这个状态改变时,我们就会触发useEffect的第一个函数

模拟component­Did­Mount

基本语法

  useEffect(()=>{
    在组件挂载后渲染一次,可作用与ajax,类似 component­Did­Mount
  },[])

实现一个ajax请求

import React, { useState, useEffect } from 'react';

const App: React.FC = () => {

  const [robotGallery, setRobotGallery] = useState<any>([])

  // 第二个参数传入一个空数组 那就代表,第一个参数只会在渲染的时候触发一次
  useEffect(()=>{
    fetch("https://jsonplaceholder.typicode.com/users")
    .then((response) => response.json())
    .then((data) => setRobotGallery(data));
  },[])
  return (
    <div >


      <div >
          {robotGallery.map((r) => (
            <p key={r.id}>{r.name}</p>
          ))}
        </div>
    </div>

  )

}

export default App;

从上面我们可以看到,我们将useEffect的第二个参数设置为[]空数组,此时,第一个参数只有在组件挂载之后调用一次。

useEffect第一个参数函数返回值

使用这个函数返回值,能让我们模拟出component­Will­Unmount操作

useEffect的第一个参数可以返回一个函数,当页面渲染了下一次更新的结果后,执行下一次useEffect之前,会调用这个函数。这个函数常常用来对上一次调用useEffect进行清理。 React能保证 在DOM 更新之后再执行 回调函数

import React,{useState,useEffect} from 'react'
import Dep from './Dep'
export default function Father  (props){
    const [info,setInfo] = useState("Fahter info")
    const [count,setCount] = useState(1)
    const callback = ()=>{console.log('Dep的callback')}
    // 假设有一个发布订阅 类 dep
    useEffect(()=>{
       console.log("订阅")
       Dep.on('init',callback)
      return ()=>{
        // 删除订阅
         Dep.off('init',callback)
        console.log("执行清除")
      }
    },[count])
    const params = useParams();
    console.log("Father渲染。。。")
    return (
        <>
          <p>count:{count}</p>
          <p onClick={()=>{setCount(count+1)}}>点击更改count</p>
        </>
    )
}

可以看到,我们每次点击更改时,都会让count+1,然后useEffect中执行第一个参数函数,当下一次count发生变化时(页面渲染之后),就会执行上一次的参数函数回调,再执行这次的useEffect的第一个参数函数,从而达到清除的作用。 执行顺序为:
count+1 ---> 页面渲染 ---> 执行上一个 useEffect 回调 ---> 执行这次的 useEffect 参数函数

useEffect 使用 async/await

useEffect的第一个参数是不能为async函数的,那么我们应该怎么在useEffect中使用async呢?

import React, { useState, useEffect } from 'react';

const App: React.FC = () => {

  const [robotGallery, setRobotGallery] = useState<any>([])

  
  useEffect(()=>{
    const fetchData = async()=>{
      const res = await fetch("https://jsonplaceholder.typicode.com/users");
      const data = await res.json()
      setRobotGallery(data)
    }
    fetchData();
  },[])
  return (
    <div >


      <div >
          {robotGallery.map((r) => (
            <p key={r.id}>{r.name}</p>
          ))}
        </div>
    </div>

  )

}

export default App;

我们可以包装一个async 函数 在useEffect中使用就行了

useContext

在类组件中,我们通过Provider的特性,可以跨组件通信,而使用了无状态组件,我们也可以使用useContext来实现跨组件通信: 新建一个src/components/data

const  defaultContext = {userName:"啊祝"}
export default defaultContext

新建一个src/components/appContext

import React from "react"
import defaultContext from "./data"
const appContext = React.createContext(defaultContext);
export default  appContext

在我们的index中写入:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import appContext from "./context/appContext"
import defaultContext from "./context/data"
ReactDOM.render(
  <React.StrictMode>
    <appContext.Provider value={defaultContext}>  // 注入
      <App />
    </appContext.Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

在子代组件中,如果是不用hook的话我们是这么做的:

import React from "react";

import appContext from "../context/appContext"


const Roboot> =({id,name,email})=>{
    return (
    <appContext.Consumer>
      {
        (value)=>{
          return (
            <div >
  
            <h2>{name}</h2>
            <p>{email}</p>
            <p>作者:{value.userName}</p>
          </div>
          )
        }
      }
    </appContext.Consumer>
    )
}
export default Roboot;

使用了useContext我们是这么做的

import React,{useContext} from "react";

import appContext from "../context/appContext"


const Roboot> =({id,name,email})=>{
    const value = userContext(appContext)
    return (

    
            <div >
  
            <h2>{name}</h2>
            <p>{email}</p>
            <p>作者:{value.userName}</p>
          </div>
          
    


    )
}
export default Roboot;

从上面的代码我们可以看到:我们使用useContext简化了代码,提升了效率

useRef

使用useRef能获取到ref对象,ref代表了当前的对象或者是组件实例:

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在

import React, { useState, useRef } from 'react'
const GrandSon = (props) => {
    const [value, setValue] = useState("qwersss")
    const inputEl = useRef(null);  // 
    const onButtonClick = () => {
        console.log(inputEl.current.value);  // 获取到了 value 的值
        // `current` 指向已挂载到 DOM 上的文本输入元素
        inputEl.current.focus();
    };
    console.log("GrandSon执行。。。")
    return (
        <>
            <p>{value}</p>
            <input ref={inputEl} value={value} type="text" onChange={(e)=>setValue(e.target.value)}/>
            <button onClick={onButtonClick}>Focus the input</button>
        </>
    )
}
export default GrandSon

我们使用了 useRef 设置了 inputElinput 元素的 ref对象,相当于 类组件的 React.createRef()
我们结合useEffectuseRef可以模拟componentDidUpdate

模拟componentDidUpdate
const GrandSon = (props) => {
    const [count,setCount] = useState(0);
    const init = useRef(true);
    useEffect(()=>{
        if(init.current){  // 当第一次进来时
            init.current = false;  // 设置为false 那么接下来的 useEffect都会走 else
        }else{
            document.title = `count:${count}`
        }
    },[count])
    return (
        <>
         <p>count:{count}</p>
         <p onClick = {()=>{setCount(count+1)}}>chang Count</p>
        </>
    )
}
export default GrandSon

首先我们先明确一个概念,就是componentDidUpdate是除去第一次初始化之后的useEffect。那么我们可以使用useRef给一个值初始值true,当第一次进入useEffect中,就将这个值置否,就能实现模拟componentDidUpdate

自定义hooks

对于公共逻辑我们可以做成一个自动义hook,比如:日志输出,双向绑定等等。
对于hook,我们通常以useXXX开头,下面我们看一个简单的例子:
自定义修改title
useTitle:

import {useEffect,useRef} from "react"
function useTitle(title) {
    useEffect(
      () => {
        document.title = title;
        return () => (document.title = "主页");
      },
      [title]
    );
  }
export {useTitle}

test:

import React, { useState } from 'react'
import {useTitle} from "../hooks/index"
const Test = (props) => {
    const [count,setCount] = useState(0);
    const init = useRef(true);
    useTitle("初始化页面");
    return (
        <>
         <p>count:{count}</p>
         <p onClick = {()=>{setCount(count+1)}}>chang Count</p>
        </>
    )
}
export default Test

下面再看一下双向绑定的例子: useBind:

function useBind(val){
    const [value,setValue] = useState(val);
   const onChange = (e)=>setValue(e.target.value)
   return {
    value,
    onChange
   }
}

Test:

import {useBind} from "../hooks/index"
const GrandSon = (props) => {
    const bindData = useBind("qwerr");
    return (
        <>
            <p>{bindData.value}</p>
            <input {...bindData} />   // 这一个有些同学可能会看不懂,其实就是 <input value={bindData.value} onChange={bindData.bindData} >
        </>
    )
}