react学习笔记

832 阅读12分钟

要点:

  • react是单项数据绑定,单项数据流

  • vue是双向数据绑定,单项数据流

  • react中所有的组件都是函数

一、创建 React 应用程序

1. 脚手架搭建

creat-react-app react创建项目的脚手架

npx create-react-app my-react(自定义项目名)

cd my-react

npm run start

成功页面:

image.png

2. 文件

  • index.js :react入口文件,要引入:
import React from 'react';
import ReactDOM from 'react-dom';

3. 类名

  • 类名需要写成className,如果有两个className,后者会替换前者。「与vue不同,vue会自动合并css样式」。不过可以自己手动合并
    let cls = 'box'
    ...
    <div className={'aaa' + cls}></div>
    
  • for 改为 htmlFor react中,点击checkbox后的label时,也需要选中checkbox。
    <input type="checkbox" id="aa"/>
    <label htmlFor="aa">选择</label> // htmlFor服务于id
    

4. style行内样式

原生写法:

<h1 style="color:red"></h1>

react 中会报错,正确写法是变量里套个对象

  • 连词写成小驼峰形式
<h1 style={{color:'red', fontSize:'20px'}}></h1>

// 或
let sty = {color:'red'}
<h1 style={ sty }></h1>

5.普通对象不能直接作为react的儿子存在

例如查看对象内容,需要转为字符串「JSON.stringify()」

二、核心概念

1. 元素渲染

更新UI界面:ReactDom.render()

2. 组件 & props

(1)组件概念

  • 组件都是函数,可分为2类:
    • 静态组件(函数式组件) hooks:专门为函数式组件准备的 「为了解决函数式组件没有状态,以及没有钩子函数的问题」

      function App(){
          return <>
              <h1>
                  函数式组件
              </h1>
          </>
      }
      
    • 类组件

      class App extends React.Component {
          render() {
              return (
               <h1>类组件</h1>
              )
          }
      }
      
  • 组件名称必须以大写字母开头(使用时)。<Welcome name="sara"/>
  • 组件可以拆分提取/组合
  • Props是只读的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

(2)父子组件通信

简单实现父组件给子组件传参,子组件展示li列表; 子组件通过调用父组件传入的方法修改父组件数据。

a)类组件方式

parent.jsx

import React, { Component } from 'react'
import Child from './child'

export default class Parent extends React.Component {
    state = {
        list: [
            {
                name: '张三',
                age: 13
            },
            {
                name: '李四',
                age: 13
            }, {
                name: '王五',
                age: 13
            }, {
                name: '刘六',
                age: 13
            }
        ]
    }

// 子组件删除父组件数据
    parent_Del(n) {
        let temp = [...this.state.list]
        temp.splice(n, 1)
        this.setState({
            list: temp
        })
    }

    render() {
        let { list } = this.state
        return (
            <div>
                父组件
                <Child data={list} onDel={this.parent_Del.bind(this)} />
            </div>
        )
    }
}

child.jsx

import React, { Component } from 'react'

export default class Child extends React.Component {
    child_Del(n) {
        this.props.onDel(n)
    }
    render() {
        let { data = [] } = this.props
        return (
            <div>
                子组件
                <ul>
                    {
                        data.map((item, index) => {
                            return (
                                <li key={item.name}>
                                    name: {item.name} <br />
                                    age: {item.age}<br />
                                    <button onClick={this.child_Del.bind(this, index)}>删除</button>
                                </li>
                            )
                        })
                    }
                </ul>
            </div>
        )
    }
}

b)函数式组件方式

parent.jsx

function Parent() {
    let data = [
        {
            name: '张三',
            age: 13
        },
        {
            name: '李四',
            age: 13
        }, {
            name: '王五',
            age: 13
        }, {
            name: '刘六',
            age: 13
        }
    ]
    const [list, setList] = useState(data)
    const parent_Del = (n) => {
        let temp = [...list]
        temp.splice(n, 1)
        setList(temp)
    }
    return (
        <div>
            父组件
            <Child data={list} onDel={parent_Del} />
        </div>
    )
}

export default Parent

child.jsx

function Child(props) {
    let { data = [] } = props
    const child_Del = (n) => {
        props.onDel(n)
    }
    return (
        <div>
            子组件
            <ul>
                {
                    data.map((item, index) => {
                        return (
                            <li key={item.name}>
                                name: {item.name} <br />
                                age: {item.age}<br />
                                <button onClick={() => { child_Del(index) }}>删除</button>
                            </li>
                        )
                    })
                }
            </ul>
        </div>
    )
}

export default Child

c)插槽

react中的插槽,通过 children 接收

例如在父组件中写入插槽内容:

 <Child data={list}>
    <p>这里是插槽</p>
 </Child>

子组件:children 接收展示

function Child(props) {
    let { data = [], children } = props
    return (
        <div>
            <h2>子组件</h2>
            {children}
        </div>
    )
}

d)自定义结构

例如用户想通过父组件自定义子组件的结构,可以传递一个render。

子组件先判断是否有render,有则使用render中传入的内容,没有再使用默认的内容

父组件:

<Child
    data={list}
    render={(item) => {
        return <h1>姓名:{item.name};年龄:{item.age}</h1>
    }}>
</Child>

子组件:

return (
    <li key={item.name}>
        {
            render ?
                render(item) :
                <span>
                    name: {item.name} <br />
                    age: {item.age}<br />
                </span>
        }

        <Button type="primary" onClick={() => { child_Del(index) }}>删除</Button>
    </li>
    )

(3)受控组件与非受控组件

在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。【官方推荐使用受控组件来处理表单数据】

受控组件:受自身控制的组件

<input
    type="text"
    value={name}
    onChange={(e) => { this.onChange(e) }} />

非受控组件:不受当前组件控制

  • 使用React.createRef()定义,this.xxx.current,value获取组件值
class input extends React.Component {
    state = {
        name: '赵彤'
    }
    www = React.createRef()
    componentDidMount() {
        this.www.current.value = this.state.name
    }
    render() {
        return (
            <div>
                <input type="text" ref={this.www} />
            </div>
        )
    }
}

3. State & 生命周期

(1) props

称为属性,父组件传进来的数据

(2) State

  • State 与 props 类似,但 state 是私有的,并且完全受控于当前组件
  • 称为状态,组件自己独享的数据
class App extends React.Component {
  constructor() {
    super();  // es6继承必须用super调用父类的constructor
    this.state = { };
   }
    render() {
        return (
         <h1>类组件</h1>
        )
    }
}

不写constructor(props),在render中也可以获取到props (this.props)

(3) 生命周期 & 钩子函数

类组件有钩子函数,函数式组件没有钩子函数

  • 挂载(mount):

    当组件实例被创建并插入Dom中时,其生命周期调用顺序如下:

    • constructor()

    • static getDerivedStateFromProps() :「新增」

      • 会在render之前调用,初始化时和更新后都会调用。

      • 不能和 componentWillMount 同时存在。(componentWillMount/UNSAFE_componentWillMount是同一个意思只是版本不同写法不同,目前都不推荐使用了,即将废除。相当于vue中的beforeMount)

    • render()

    • componentDidMount():「常用」

      方法会在组件加载完成(被渲染到 DOM 中)后触发,相当于vue中的 mounted 。

    ajax请求一般放在componentDidMount()

  • 更新:

    当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

    • static getDerivedStateFromProps() 「新增」

      在render之前执行调用

    • shouldComponentUpdate()「新增」

      • 这是一个用来优化的钩子函数.根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响

      • 当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true

         shouldComponentUpdate(nextProps, nextState)
      
    • render()

    • getSnapshotBeforeUpdate() 「新增」「常用」

    • componentDidUpdate()

      • 相当于vue中的updated
  • 卸载(unmount):

    组件从Dom中被移除时触发。

    • componentWillUnmount()「常用」 方法清除组件内容,例如清除计时器,相当于vue中的beforeDestroy
  • 错误处理

    当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

    • static getDerivedStateFromError()

      在后代组件抛出错误后被调用,它将抛出的错误作为参数,并返回一个值以更新 state

    • componentDidCatch()

      在后代组件抛出错误后被调用

  • 其他

    forceUpdate() 强制重新渲染。

(5)setState()

用于类组件

class BusinessInfo extends React.Component {
    state = {
        count: 100
    }
    add(num) {
        this.setState({
           
            count:this.state.count + num
        })
    }
    minus(num) {
        this.setState({
            count:this.state.count - num
        })
    }
    render() {
        let { count } = this.state
        return (<div>
            <h1>当前商品数量:{count}</h1>
            <Button onClick={() => { this.add(10) }}>+</Button>
            <Button onClick={() => { this.minus(5) }}>-</Button>

        </div>
        )
    }
}
  • (1)不要直接修改state,因为代码不会重新渲染组件。应该使用react内部提供的setState()。

    this.state.count = this.state.count + num

    count数据已经改变了,但是视图不变,因为单项数据流,所以不会重新渲染组件

    this.setState({
       count:this.state.count + num
    },function(){
      console.log('数据更新完成')
    })
    
    • setState接受第二个参数,当数据更新完成后触发 构造函数是唯一可以给this.state赋值的地方。
  • (2)State的更新大部分情况下是异步的

    考虑性能,react可能会将多个setState()调用合并成一个。且this.props和this.state可能会异步更新,所以不能完全依赖它们来更新下一个状态,以下方法可以解决更新问题

    写法1:
    this.setState((state,props) => {
      counter:state.counter + props.increment
    })
    
    写法2:
    this.setState(function(state,props){
        return {
         counter:state.counter + props.increment
        }
    })
    
  • (3)State的更新会被合并

    这里说的合并,是指如果通过setState()方法更新数据,原state中的属性在展示上不会丢失。

    「这里区别于函数式组件使用useState,未更新的内容会丢失,详细内容见下文Hook里的userState总结」

(6)单向数据流

任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。

4. 事件处理

  • react事件命名使用小驼峰,不是纯小写

  • 使用JSX语法,要传入一个函数作为事件处理函数,而不是一个字符串。

<button onClick={activateLsasers}> Activate Lasers </button>
  • 阻止默认行为,要显式的使用 preventDefault

  • 向事件处理程序传递参数

    类数组 ,需要保证函数中的this是当前实例

    方式1:箭头函数方式 「事件对象必须显式传递」
     <button onClick={(e) => this.deleteRow(id, e)}> Delete Row </button>
     // 此时this,也是render函数中的this,是当前实例
    
    方式2:bind方式 「事件对象以及更多参数将会被隐式传递」
    <button onClick={this.deleteRow.bind(this,id)}> Delete Row </button>
    // 此时的this也是当前实例
    

this

在javaScript中,class的方法默认不会绑定this,如果忘记绑定this.handleClick,并把它传入onClick,当调用这个函数的时候,this的值为undefined

class Toggle extends React.Component {
  constructor(props){
    super(props)
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this) // *重点*
  }
}

5. 条件渲染

  • 元素变量
  • 与运算符 &&
  • 三目运算符

阻止组件渲染:「根据props中的属性值进行条件渲染」

6. 列表 & key

  • 使用map()方法遍历数组,将遍历后的数据放在标签里,如:<li>标签,再将<li>标签组成的数组插入到<ul>元素中,最后渲染进Dom

    {
        list.map(item => {
            return <li key={item.name}> {item.name} </li>
        })
    }
    
  • key:给数组中的每一个元素赋予一个确定标识

    • 独一无二的字符串,通常使用数据中的id
    • 实在没有id等唯一标识,可以使用索引index(前提是静态数据,不会引起列表顺序变化的,否则会引起bug)
    • 如果不指定key,react会默认index作为key【同vue】
    • 位置:一个好的经验法则是:在 map() 方法中的元素需要设置 key 属性。

7. 表单

(1)input

export default class input extends React.Component {
    state = {
        name: '赵彤'
    }
    onChange(e){
        console.log(e.target.value);
        this.setState({
            name:e.target.value
        })
    }
    render() {
        let { name } = this.state
        return (
            <div>
                <p>{name}</p>
                <input
                    type="text"
                    value={name}
                    onChange={(e) => { this.onChange(e) }} />
                    // 在react中,onChange和onInput效果一样
            </div>
        )
    }
}

8. 状态提升

官方解释:多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。

也就是:当前组件私有的state,提升到上层组件(具体父亲还是爷爷组件看情况)统一管理,是一种思想。

三、Hook

React提供的Hooks函数,目的是让”函数式组件“能拥有类似于“类组件”的一些效果

  • useState 在函数式组件中的应用状态
  • useEffect 拥有生命周期
  • useRef 让其使用DOM
  • useReducer 让其能够像redux一样管理状态
  • ......

Hook 只能用做最顶层作用域,不能写到{}这种块级作用域中,否则会报错。

1. useState

(1)基本使用

使用 useState,主要解决了函数式组件没有自己的状态,及没有钩子函数特性的问题。

普通写法让数据改变,但视图不更新,要更新视图就需要使用hook中的useState

import React, { useState } from 'react';
function BusinessInfo1() {
    let [getCount, setCount] = useState({
        count: 100,
        name: 'A'
    })
    const add = (num) => {
        // count = count + num // 数据变化,视图未更新
        setCount({
            ...getCount,
            count:getCount.count + num
        })
    }

    const minus = (num) => {
        setCount({
            ...getCount,
            count:getCount.count - num
        })
    }
    return (
        <div>
            <h1>{getCount.name}当前商品数量:{getCount.count}</h1>
            <Button onClick={() => { add(10) }}>+</Button>
            <Button onClick={() => { minus(5) }}>-</Button>
        </div>
    )
}

let [getCount, setCount] = useState({ count: 100, name: 'A' })

  • useState 接收的参数是初始值
  • 使用useState存在一个问题,它拿到的结果默认不会合并,而是顶替,所以需要手动把其他属性补上 ...getCount

(2)异步操作情况

例如在累计计数时加一个定时,定时器到时间输出的还是原来值,并不是最新的值,原因是每次都是一个独立作用域。

function UseState1() {
    let [count, setCount] = useState(100)
    console.log(`第${count}次`);
    const log = () => {
        setTimeout(() => {
            console.log('log:', count);
        }, 2000)
    }
    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => { setCount(count + 1) }}>+</button>
            <button onClick={log}>log</button>
        </div>
    )
}

image.png

如何拿到最新值呢?可以将新数据存起来。

方法1:原生方法「创建外部作用域」

注意: //*** 为改动点

let outCount = 100       //***
function UseState1() {
    let [count, setCount] = useState(100)
    console.log(`第${count}次`);
    const log = () => {
        setTimeout(() => {
            console.log('log:', outCount);       //***
        }, 2000)
    }
    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => { setCount(count + 1); outCount = count + 1 }}>+</button>  //***
            <button onClick={log}>log</button>
        </div>
    )
}

方法2:react思想「useRef(),原理也是创建外部作用域」

function UseState1() {
    let [count, setCount] = useState(100)
    const ref = useRef()       //*** 
    const log = () => {
        setTimeout(() => {
            console.log('log:', ref.current);   //*** 
        }, 2000)
    }
    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => { setCount(count + 1); ref.current = count + 1 }}>+</button>     //*** 
            <button onClick={log}>log</button>
        </div>
    )
}

方法3:回调函数

function UseState1() {
    let [count, setCount] = useState(100)
    console.log(`第${count}次`);
    const log = () => {
        setTimeout(() => {
            setCount((newVal) => {    //***
                console.log(newVal);
                return newVal + 10
            })
        }, 2000)
    }
    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => { setCount(count + 1) }}>+</button>
            <button onClick={log}>log</button>
        </div>
    )
}

2. useEffect

类组件有钩子函数,函数式组件没有钩子函数,所以就需要用到useEffect

useEffect(() => {
  console.log('componentDidMount + componentDidUpdate');
},[])
  • useEffect接收两个参数,如果不写第二个参数,效果相当于componentDidMount + componentDidUpdate,初始加载会执行,组件更新完也会执行。

  • 给第二个参数添加一个空数组,相当于只执行componentDidMount。(初始加载会执行,组件更新不执行)

  • 设置依赖性:数组里放的是依赖项,只有依赖项发生改变的时候,才会触发这个钩子

  • useEffect 的回调函数

     useEffect(() => {
            return () => {
    
            }
        })
    
    • 更新时会触发,回调函数可以相当于componentDidUpdate的效果。

3. useRef

让其可以进行DOM操作.

(1)使用ref

let getText = useRef()

(2)想要获取哪个dom元素,就给它的ref属性赋值

<span ref={getText}>hello</span>

(3)基于current属性可以获取到DOM元素

<button onClick={()=>{
   console.log(getText.current.innerHTML); // hello
 }}> useRef</button>

4. memo「处理组件」

  • memo处理过的组件,相当于自动执行的,类似于类组件的shouldComponentUpdate

  • 只要传进来的数据不发生改变,那么对应的组件就不会重新渲染

    Child = memo(Child)
    
  • React.memo 类似于 PureComponent

    两者区别:

    • React.memo 只判断 props是否改变,改变触发更新, state是否变化不处理 ;PureComponent都会处理。

5. React.PureComponent

  • 继承 React.PureComponent,其内部实现了shouleComponentUpdate,只实现了浅层比较(引用数据类型,只比较第一层,内部检测不到)

    shouleComponentUpdate(nextProps, nextState) 接收2个参数

  • React.Component存在一个问题,父组件更新后,如果没有做任何处理,子组件的render函数都会重新执行一遍。如果某个组件不需要更新,可以使用shouleComponentUpdate钩子函数进行判断

6. useMemo「处理数据」

useMemo是配合memo使用的,它可以缓存对象地址。

它会在某个依赖项改变时重新计算值,这种优化有助于避免在每次渲染时都进行高开销的计算。

let obj = useMemo(() => {
        
}, [count])
  • 参数1:回调函数
  • 参数2:依赖 「依赖(count)发生改变,给obj一个新地址;没有改变不会赋值新地址」
  • obj:回调结果

memo和useMemos示例代码

function Child(props) {
    console.log('render');
    return <h1>{props.data.count}</h1>
}

const Childs = React.memo(Child)

function useMemos() {
    let [count, setCount] = useState(100)
    let [age, setAge] = useState(10)
    let obj = useMemo(() => {
        return {
            count: count
        }
    }, [count])
    return (
        <div>
            <Childs data={obj} />
            <button onClick={() => { setCount(count + 3) }}>count + </button>
            <h2>{age}</h2>
            <button onClick={() => { setAge(age + 1) }}>age + </button>
        </div>
    )
}

7. useCallback

React.memo, useMemo, useCallback 三个是一个组合,主要用于优化.

useCallback用于缓存函数地址

 let f = () => {
   setCount(count + 10)
 }
 f = useCallback(f, [])

return (
    <div>
        <Childs data={obj} onChangeCount={f} />
        <button onClick={() => { setCount(count + 3) }} >count + </button>
        <h2>{age}</h2>
        <button onClick={() => { setAge(age + 1) }}>age + </button>
    </div>
)

8. useReducer

useReducer 是useState的一个替代方案

四、路由

1. 安装

在浏览器中使用

npm install react-router-dom

2. 基础组件

(1) 路由组件(router components)

  • <BrowserRouter> :浏览器模式
  • <HashRouter>:hash模式 使用路由组件,要确保在根组件上使用。

(2) 路由匹配组件(route matchers components)

  • <Route>
  • <Switch><Switch>里可以包裹多个<Route>,浏览器地址会依次比较path地址,匹配则显示,不匹配返回null。
 <Switch>
     <Redirect path='/' exact to='/introduce'></Redirect>
     <Route path='/introduce' component={Introduce}></Route>
     <Route path="/Users" component={UserInfo}></Route>
 </Switch>

exact

当前路由path的路径采用精确匹配

(3) 导航组件(navigation components)

  • <Link>: 会被渲染为一个<a>标签

    <Link to="/">Home</Link>
    
  • <NavLink>

  • <Redirect>:强制跳转某页面可以使用这个标签,例如是否是登录状态。

引入依赖

import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";

3. 其他属性

lazy懒加载

import React, { lazy } from 'react'
const login = lazy(() => import('@/views/Login'))

Suspense

延迟加载