初识React之旅 | 青训营笔记

42 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的的第4天

React 生命周期

image.png

1、hello world程序编写(index.js)

//1、引入React两个核心模块
import React from 'react';
import ReactDOM from 'react-dom';

//2、通过JSX语法将组件/标签渲染到指定标签上
ReactDOM.render(
    <h1>
        hello world!
    </h1>
    , document.getElementById('root'));
    
//ReactDOM.render(参数1,参数2)
//参数1 是JSX语法的标签/组件
//参数2 是要把参数1这个标签渲染到的位置

2、组件化开发(app.js)

//1、导入React核心模块
import React from 'react'

//2、定义组件类
class Hello extends React.Component{   //类
    render(){     //函数
        return (   //返回值
            <div>
                hello world !!! 我是组件222
            </div>
        )
    }
}

//3、导出组件
export default Hello

//4、导出并定义组件类
export default class Hello extends React.Component{   //类
    render(){     //函数
        return (   //返回值
            <div>
                hello world !!! 我是组件222
            </div>
        )
    }
}

//页面渲染
//import 组件名 from '文件路径'
import Hello from './App'   

//1、导入Hello组件  (首字母必须大写)
ReactDOM.render(
    <Hello />        // 2、使用Hello组件  (首字母必须大写)   
    , document.getElementById('root'));

//注意:Hello是组件名,在使用的时候就应该写JSX标签写法,而不能直接写Hello
//!!注意:
//1、定义组件的时候,return 后面只能有一个根标签,不能有多个,但这个标签内部可以有其他多个标签
//2、使用组件的时候,首字母必须大写
//3、如果最外层实在不想放置一层div根目录,可以使用 `<></>`空标签或者`<Fragment></Fragment>` 标签代替

3、JSX注意的格式

1、React的JSX是使用大写和小写字母来区分本地组件和HTML组件

2、JSX和html的标签属性的区别

image.png

eg:渲染图片
import React, { Component } from 'react'
var MyImg = require('./assets/01.jpg')

export default class App2 extends Component {
    render() {
        return (
            <>
                <label htmlFor="ipt">label</label>
                <input type="text" id="ipt" className="ipt" style={{background: 'pink', color: 'green'}} />
                <hr/>
                <img src={MyImg} alt=""/>
            </>
        )
    }
}

4、JSX变量引用、三目运算符、for循环

import React, { Component } from 'react'

let name = "小明", num1=20, num2=30, arr=[1, 2, 3, 4, 5]

export default class App3 extends Component {
  render() {
    return (
      <div>
        {/* 这是注释的格式 */}
        {/* JSX中引用变量需要加单花括号 */}
        <p>{name}</p>

        {/* 三目运算符的使用 */}
        <p>num1和num2中比较大的是:{num1>num2? num1: num2}</p>
        <p>{gender === 0 ? '男' : (gender === 1 ? '女' : '保密')}</p>

        {/* for循环的使用 */}
        <ul>
          {/* 数组名.map(函数) */}
          {
            //格式1:
            arr.map((v,k)=>(
              <li key={k}>{v}</li>
            ))
          }
          {
            //格式2:可以把下面的大括号和return换成小括号
            arr.map((v,k)=>{    
              return <li key={k}>{v}</li>
            })
          }
        </ul>
      </div>
    )
  }
}

5、事件、state与setState

//事件讲解
import React, { Component } from 'react'

//1、实现点击弹框效果(事件基本格式)
export default class App5 extends Component {
    handleClick(){  
        alert(132456)
    }
    render() {
        return (
            <div>
                <button onClick={this.handleClick}>按钮</button>
            </div>
        )
    }
}

//2、实现累加的功能 (状态的使用1)
export default class App4 extends Component {
    constructor(props){
        super(props)
        this.state = {
            num: 0
        }
        // 按钮3的函数写法的前提
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(){
        this.setState({
            num: this.state.num+1
        })
    }
    render() {
        return (
            <div>
                <h2>{ this.state.num }</h2>
                {/* 通过bind来改变this的指向 */}
                {/* <button onClick={this.handleClick.bind(this)}>按钮1</button> */}
                {/* 箭头函数默认没有this,所以this指向数组对象 */}
                {/* <button onClick={()=>this.handleClick()}>按钮2</button> */}
                <button onClick={this.handleClick}>按钮3</button>
            </div>
        )
    }
}


//3、实现双向数据绑定 (状态的使用2)
export default class App5 extends Component {
    constructor(p){
        super(p)
        this.state = {
            name: "你好,世界"
        }
    }
// 写在construtor中的state也可以简写
    state = {
        name: "你好,世界"
    }
    handleChange(e){
        console.log(e.currentTarget.value)
        console.log(e.target.value)
        this.setState({
            name: e.target.value
        })
    }
    render() {
        return (
            <div>
                <h2>{ this.state.name }</h2>
                <input type="text" value={this.state.name} onChange={this.handleChange.bind(this)} />
            </div>
        )
    }
    //简写
     render() {
        return (
            <div>
                <h1>{this.state.username}</h1>
                <button onClick={this.handleClick.bind(this)}>按钮</button>
            </div>
        )
    }
}

5.1 state对数组的修改

import React, { Component } from 'react'

export default class App9 extends Component {
    state = {
        arr: ["张飞", "赵云", "刘备"]
    }
    render() {
        return (
            <div>
                <ul>
                    {
                        this.state.arr.map((item, index)=>{
                            return <li key={index}>{item}</li>
                        })
                    }
                </ul>
                <button onClick={this.addFn.bind(this)}>追加一项到arr的最后</button>
            </div>
        )
    }
    // 追加一项到arr的最后
    // 修改state的方式只能是setState
    // 数组的push方法会修改原数组
    // this.state.arr.push() 会造成直接修改了state中的arr
    // 修改数组,不能直接修改this.state.arr
    
    addFn(){
        // 如果我声明一个变量,等于this.state.arr,并且让这个变量和this.state.arr完全脱离关系[深拷贝]
        let newArr = JSON.parse(JSON.stringify(this.state.arr));
        // 往newArr中push一项
        newArr.push('关羽');
        // 把最新的newArr赋值给arr
        this.setState({
            arr: newArr
        })
    }
}

6、函数式组件

// App.jsx
import React from 'react'

function App(){
    return <h2>你好世界</h2>
}

export default App;
//1、没有this,组件中打印this,会得到undefined
//2、没有state,数据的控制不受state管理,需要使用hooks
//3、没有生命周期

6.1 父子传参

6.1.1 父传子

// 父组件
import Sub from './Sub'
let msg = "你好世界"
export default function App(){
    return <Sub msg={msg} />
}
// 子组件
export default function Sub(props) {
  return <h2>{props.msg}</h2>
}

6.1.2 子传父

// 父组件
import Sub from './Sub'
let msg = "你好世界"
export default function App(){
    const fn = function(arg){
        console.log(arg)	// 123
    }
    return <Sub msg={msg} fn={fn}  />
}

// 子组件
export default function Sub(props) {
  return (
      <>
        <h2>{props.msg}</h2>
        <button onClick={()=>props.fn(123)}>将123传递给父组件</button>
      </>
  )
}

6.2 Hooks

6.2.1 useState--定义响应式数据

import React from 'react'

function App(){
    const [num, setNum] = React.useState(1)
    return (
        <>
            <h2>数字为:{num}</h2>
            <button onClick={()=>setNum(num+1)}>累加</button>
        </>
    )
}
export default App;

6.2.2 useEffect--有时我们需要在挂载后请求数据、销毁前清除数据,或是在更新后获取数据,那么就需要借助useEffect。

//模拟挂载后--在挂载后请求数据
import React, {useEffect} from 'react'
export default function App(){
    useEffect(()=>{
        // do some requests
        console.log('挂载后')
    })
    return <h2>你好世界</h2>
}

//模拟销毁前--垃圾回收或数据清理
import React, {useEffect} from 'react'
export default function App(){
    useEffect(()=>{
        // do some requests
        return ()=>{
            // do some clear
            console.log('销毁前')
        }
    })
    return <h2>你好世界</h2>
}
//在useEffect的callback中返回一个函数,代表“销毁前”。如果想要体验销毁前,可以在入口文件index.js中添加:
setTimeout(()=>{
    ReactDOM.render(
        <input type="text" />,
        document.getElementById('root')
    )
}, 3000)

//模拟检测更新
import React, {useEffect, useState} from 'react'
export default function App(){
    const [num1, setNum1] = useState(1)
    const [num2, setNum2] = useState(1)
    useEffect(()=>{
        console.log('num1更新了')
    }, [num1])
    return (
        <>
            <h2>数字1:{num1}</h2>
            <button onClick={()=>setNum1(num1+1)}>累加num1</button>
            <hr />
            <h2>数字2:{num2}</h2>
            <button onClick={()=>setNum2(num2+1)}>累加num1</button>
        </>
    )
}
//注意!!
//数组为空,代表不检测数据更新
//数组中填写的变量,才会被检测更新
//如果整个页面所有数据都需要检测更新,那么可以直接不写数组

6.3 createContext

6.3.1 Provider与Consumer--通过createContext创建的上下文空间自带Provider(提供器)与Consumer(接收器)

import React, {createContext} from 'react'
const MsgContext = createContext()
// 子组件
function Sub(){
    return (
        <MsgContext.Consumer>
            {
                ({msg})=><h2>子组件:{msg}</h2>
            }
        </MsgContext.Consumer>
    )
}
// 父组件
function Father(){
    return <Sub />
}
// 顶级组件
export default function App(){
    return (
        <MsgContext.Provider value={{msg: "你好世界"}}>
            <Father  />
        </MsgContext.Provider>
    )
}

6.3.2 useContext--上下文空间创建后,子组件也可以使用useContext这个Hook调用上下文空间。

import React, {createContext, useContext} from 'react'
const MsgContext = createContext()
// 子组件
function Sub(){
    const {msg} = useContext(MsgContext);		// 使用useContext调用上下文空间
    return <h2>子组件:{msg}</h2>
}
// 父组件
function Father(){
    return <Sub />
}
// 顶级组件
export default function App(){
    return (
        <MsgContext.Provider value={{msg: "你好世界"}}>
            <Father  />
        </MsgContext.Provider>
    )
}

6.4 useRef--获取某个组件或元素

//1、不受控组件代表表单元素的值不受state控制,那么获取表单元素的值便只能空过ref获取(函数式组件中使用useRef获取)。
import React, {useRef} from 'react'
export default function App(){
    const element = useRef(null);
    return (
        <>
            <input type="text" ref={element}  />
            <button onClick={()=>console.log(element.current.value)}>获取input的值</button>
        </>
    )
}
//2、受控组件代表表单元素的值受state控制(函数式组件中可以理解为受useState控制),获取其值时直接通过state获取即可:
import React, {useState} from 'react'
export default function App(){
    const [msg, setMsg] = useState("");
    return (
        <>
            <input type="text" value={msg} onChange={(e)=>setMsg(e.target.value)}  />
            <button onClick={()=>console.log(msg)}>获取input的值</button>
        </>
    )
}

6.5 memo

实际开发中,我们会意识到一个现象:当父组件更新时,子组件会被迫更新。 这就导致性能损耗,来看以下代码:

import React, {useState} from 'react'
// 子组件
function Sub(){
    console.log('子组件被更新了')
    return <div>子组件</div>
}
// 父组件
export default function App(){
    const [msg, setMsg] = useState("你好世界");
    return (
        <>
            <h2>内容为:{msg}</h2>
            <button onClick={()=>setMsg("Hello World")}>修改Msg</button>
            <hr />
            <Sub />
        </>
    )
}
//此时,我们需要借助memo这个hook来缓存Sub组件,避免它被强制更新:
import React, {useState, memo} from 'react'
// 子组件
const Sub = memo(() => {
    console.log('子组件被更新了')
    return <div>子组件</div>
})
// 父组件
export default function App(){
    const [msg, setMsg] = useState("你好世界");
    return (
        <>
            <h2>内容为:{msg}</h2>
            <button onClick={()=>setMsg("Hello World")}>修改Msg</button>
            <hr />
            <Sub />
        </>
    )
}

6.6 useMemo与useCallback

6.6.1 useCallback

//当把触发msg修改的按钮放到子组件后,msg一修改又会触发Sub的更新:
import React, { useState, memo } from 'react'
// 子组件
const Sub = memo((props) => {
    console.log('子组件被更新了')
    return <button onClick={props.mySetMsg}>修改Msg</button>
})
// 父组件
export default function App() {
    const [msg, setMsg] = useState("你好世界");

    const mySetMsg = () => setMsg("Hello World")

    return (
        <>
            <h2>内容为:{msg}</h2>
            <Sub mySetMsg={mySetMsg} />
        </>
    )
}
//此时,只要给mySetMsg方法套上useCallback即可:
const mySetMsg = useCallback(() => setMsg(()=>"Hello World"), [])	// 此处setMsg()中需要使用callback形式

6.6.2 useMemo

//useMemo与useCallback差不多,只是useMemo需要在回调函数中再返回一个函数,我们称之为高阶函数:
const mySetMsg = useMemo(()=>{
  return () => setMsg("Hello World")
}, [])