【React】React基础(上)

React

推荐使用 antDesign组件库

可视化antV

1 React 基础

专注于构建用户界面的JavaScript库,和Vue、angular并称前端三大框架,其中react是目前世界范围内最流行的js前端框架,版本已经更新到了18。

文档(英文)https://reactjs.org/

文档(中文)https://zh-hans.reactjs.org/

新文档 https://beta.reactjs.org/

1.1 React 特点

  1. 声明式UI(jSX)

    写UI就和写普通HTML一样,抛弃命令式的繁琐实现,不需要逐步编写dom逻辑去实现。

  2. 组件化

    组件是React中最重要的内容,组件可以通过搭积木的方式拼成一个完整的页面,通过组件的抽象可以增加复用能力和提高可维护性。

  3. 一次学习,跨平台编写

    react既可以开发web应用,也可以使用同样的语法开发原生应用(react-native), 比如安卓和ios应用,甚至可以使用react开发VR应用,它像一个元框架为各种领域赋能。

1.2 环境初始化

1 使用脚手架创建项目

npx create-react-app my-app
# npx命令会帮助我们临时安装create-react-app包,然后初始化项目完成之后会自动删除,不需要全局安装create-react-app

2 项目目录说明

React: 框架核心包。

ReactDOM: 专门做渲染相关的包。

// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
// 引入根组件
import App from './App' 

// 渲染根组件App到一个id为root的dom节点上
ReactDOM.reader(
// 严格模式节点,会影响useEffect的执行时机
<React.StrictMode>
    <App/>
<React.StrictMode>,
document.getElementById('root')
)

1.3 格式化配置

  1. 安装VSCode prettier 插件

  2. 修改配置文件setting.json

    {
        "git.enableSmartCommit": true,
        // 修改注释颜色
        "editor.tokenColorCustomizations": {
            "comments": {
                "fontStyle": "bold",
                "foreground": "#71d151dd"
            }
        },
        // 配置文件类型识别
        "files.associations": {
            "*.js": "javascript",
            "*.json": "jsonc",
            "*.cjson": "jsonc",
            "*.wxss": "css",
            "*.wxs": "javascript"
        },
        "extensions.ignoreRecommendations": false,
        "files.exclude": {
            "**/.DS_Store": true,
            "**/.git": true,
            "**/.hg": true,
            "**/.svn": true,
            "**/CVS": true,
            "**/node_modules": false,
            "**/tmp": true
        },
        // "javascript.implicitiProjectConfig.experimentalDecorators": true,
        "explorer.confirmDragAndDrop": false,
        "typescript.updateImportsOnFileMove.enabled": "prompt",
        "git.confirmSync": false,
        "editor.tabSize": 2,
        "editor.fontWeight": "300",
        "[json]": {},
        "editor.tabCompletion": "on",
        "vsicons.projectDetection.autoReload": true,
        "editor.fontFamily": "Monaco, 'Courier New', monospace, Meslo LG M for Powerline",
        "[html]": {
            "editor.defaultFormatter": "vscode.html-language-features"
        },
        "editor.fontSize": 16,
        "debug.console.fontSize": 14,
        "vsicons.dontShowNewVersionMessage": true,
        "editor.minimap.enabled": true,
        "emmet.extensionsPath": [
            ""
        ],
        // vue eslint start 保存时自动格式化代码
        "editor.formatOnSave": true,
        // eslint 配置项,保存时自动修复错误
        "editor.codeActionsOnSave": {
            "source.fixAll": true
        },
        "vetur.ignoreProjectWarning": true,
        // 让vetur 使用vs自带的js格式化工具
        // uni-app和vue项目使用
        "vetur.format.defaultFormatter.js": "vscode-typescript",
        "javascript.format.semicolons": "remove",
        // 指定 *.vue 文件的格式化工具为vetur
        "[vue]": {
            "editor.defaultFormatter": "octref.vetur"
        },
        // 指定 *.js 文件的格式化工具为vscode自带
        "[javascript]": {
            "editor.defaultFormatter": "vscode.typescript-language-features"
        },
        // 默认使用prettier 格式化支持的文件
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "prettier.jsxBracketSameLine": true,
        // 函数前面加个空格
        "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
        "prettier.singleQuote": true,
        "prettier.semi": false,
        // eslint end
        // react
        // 当按tab键的时候,会自动展示
        "emmet.triggerExpansionOnTab": true,
        "emmet.showAbbreviationSuggestions": true,
        "emmet.includeLanguages": {
            // jsx 的提示
            "javascript": "javascriptreact",
            "vue-html": "html",
            "vue": "html",
            "wxml": "html"
        },
        // end
        "[jsonc]": {
            "editor.defaultFormatter": "vscode.json-language-features"
        },
        // @路径提示
        "path-intellisense.mappings": {
            "@": "${workspaceRoot}/src"
        },
        "security.workspace.trust.untrustedFiles": "open",
        "git.ignoreMissingGitWarning": true,
        "workbench.colorTheme": "Dracula Soft",
        "workbench.iconTheme": "vscode-icons",
        "window.zoomLevel": 1
    }
    

1.4 工具

vscode ErrorLens: 错误提示,实时的。

Snipaste 截图

2 JSX基础

概念:JSX是JavaScript XML(HTML)的缩写,表示在 JS 代码中书写 HTML 结构。

作用: 在 React 中创建 HTML 结构(页面 UI 结构)

优势

  • 采用类似 HTML 的语法,降低学习成本。(声明式)
  • 充分利用 JS 自身的可编程能力创建 HTML 结构。

​ JSX 并不是标准的 JS 语法,是 JS 的语法扩展,浏览器默认是不识别的,脚手架中内置的 @babel/plugin-transform-react-jsx 包,用来解析该语法。

2.1 JSX 中使用 JS 表达式

语法{js 表达式}

const name = '柯基'
<h1> 你好,我叫{name} </h1>

可以使用的表达式

  1. 字符串、数值、布尔值、null、undefined、object
  2. 加减乘除运算、
  3. fn()

特别注意

​ if 语句/switch-case语句/变量声明语句,这些叫做语句,不是表达式,不能出现在 {}中!

2.2 JSX 列表渲染

页面的构建离不开重复的列表结构,比如商品列表、图书列表,vue 中使用的是v-for, react中使用的是数组 map方法。

// eg
const songs = [
    {id: 1, name: '晚风'},
    {id: 2, name: '夏日'},
    {id: 3, name: '穠芳'}
]
// 遍历列表时,需要一个类型为 number/string 的不重复值作为key,提高React中diff算法的性能
// key 仅仅在内部使用,不会出现在真实的 dom 节点上
function App(){
    return (
    	<div className="App">
        	<ul> 
            	{ songs.map(item => <li key={item.id}>{item.name}</li>) }
            </ul>
        </div>
    )
}

条件渲染:可以是模板中写入三元运算、与或操作、抽离出的函数等,这些都可以展示出条件渲染。

2.4 JSX 样式处理

行内样式 style

function App() {
    return (
    	<div className="App">
            <div style={{color:'red'}}>this is a div</div>
        </div>
    )
}
export default App

行内样式 style 更优写法

const styleObj = {
    color: red
}

function App() {
    return (
    	<div className="App">
        	<div style={styleObj}>this is a div</div>
        </div>
    )
}
export default App

类名样式

// app.css
.active {
    color: blue;
}

// App.js
const activeFlag = false
function App() {
    return (
    	<div className="App">
        	<div className='active'>this is a div</div>
            <span className={activeFlag ? 'active':''}>动态控制active类名</span>
        </div>
    )
}
export default App

2.5 JSX 注意事项

  • jsx 必须有且只有一个根节点,如果没有根节点,可以使用<></>(幽灵节点)代替。
function App() {
    return (
    <>
        <div>app</div>
        <div>app</div>
    </>
    )
}
  • 所有标签必须形成闭合,成对闭合或者自闭和都可以。
  • JSX 中的语法更贴近 JS 语法,属性名采用驼峰命名法 class -> className for -> htmlFor
  • JSX 支持多行(换行),如果需要换行,需使用()包裹,防止出现bug。

3 组件

3.1 组件概念

image-20220607080911702

​ 上图所示的左侧页面效果对应到React中,它是一个组件树的形式。

3.1.1 函数组件

使用 JS 的函数(或箭头函数)创建的组件,就叫做函数组件

// 函数组件的创建和渲染
// 创建
function Hello () {
    return <div>hello</div>
}

// 渲染 <Hello/> 
// 渲染 <Hello></Hello>

function App () {
    return (
    	<div>
        	<Hello/>
        </div>
    )
}
export default App

约定:

  1. 组件的名称必须首字母大写,react 内部会根据这个来判断是组件还是普通HTML标签。
  2. 函数组件必须有返回值,表示该组件的 UI 结构;如果不需要渲染任何内容,则返回null。
  3. 组件就像 HTML 标签一样可以被渲染到页面中。组件表示的是一段结构内容,对于函数组件来说,渲染的内容是组件的返回值就是对应的内容。
  4. 使用函数名称作为组件标签名称,可以成对出现也可以自闭合。

3.1.2 类组件

使用 ES6 的class 创建的组件,叫做类(class)组件。

// 类组件的创建和渲染
// 创建
class HelloComponent extends React.Component{
    render() {
        return <div> 这是一个类组件!</div>
    }
}

// 渲染(成对出现/自闭合) <HelloComponent/> <HelloComponent></HelloComponent>

function App () {
    return (
    	<div>
        	<HelloComponent></HelloComponent>
        </div>
    )
}
export default App

约定:

  1. 类名称必须首字母大写。
  2. 类组件应该继承 React.Component 父类, 从而使用父类中提供的方法或属性。
  3. 类组件必须提供 render 方法,render 方法必须有返回值,表示该组件 UI 结构。

3.2 事件绑定

3.2.1 绑定

  • 语法: on + 事件名称 = {事件处理程序},如<div onClick={() => {}}>/div>

  • 事件名称采用驼峰命名方法,如: onMouseEnter、onFocus

// 函数组件绑定
function Hello () {
    const clickHandler = () => {
        console.log('函数组件的事件被触发了!')
    }
    return <div onClick={clickHandler}>hello</div>
}

// 类组件绑定
class HelloComponent extends React.Component{
    // 事件回调函数(标准写法,避免this指向不明)
    clickHandler = (msg) =>{
        console.log('类组件的事件被触发了!',msg);
    }
    render() {
        return (<div onClick={()=>this.clickHandler('疾风知行')}> 这是一个类组件!</div>)
    }
}

3.2.2 获取事件对象

  • 通过事件处理程序的参数获取事件对象e。

    // 函数组件
    function HelloFn () {
        // 定义事件回调函数
        const clickHandler = (e) => {
            // 阻止默认操作
            e.preventDefault()
            console.log('事件被触发了', e)
        }
        return (
        	// 绑定事件
            <a href="http://www。baidu.com/" onClick={clickHandler}>百度</a>
        )
    }
    

3.2.3 传递自定义参数

// 1. 只需要一个额外参数 {clickHandler ->  {() => clickHandler('自定义的参数')}
// 2.既需要e也需要额外的参数  {(e)=>clickHandler(e,'自定义的参数')}
function HelloFn () {
    // 定义事件回调函数
    const clickHandler = (msg) => {
        console.log('事件被触发了', msg)
    }
    return (
       <div onClick={() => clickHandler('传递成功')}>click me</div>
    )

3.3 组件状态

在 react hook出来之前,函数式组件没有自己的状态。

state 中尽量保持精简,如果数据是组件的状态需要去影响视图,定义到 state中。而如果我们需要的数据状态,不和视图绑定,定义成一个普通的实例属性就可以!

初始化状态 ------> 读取状态-------->影响视图

  • 通过 class 的实例属性 state 来初始化。

  • state 的值是一个对象结构,表示一个组件可以有多个数据状态。

    // 类组件
    import React from 'react'
    class Counter extend React.Component {
        // 1.初始化状态
        state = {
            // 此处可以定义各种属性,全部都是当前组件的状态
            count: 0
        }
        // 事件回调函数
        changCount = () => {
            // 3. 修改 state 中的状态 count
            // 注: 不可以直接做赋值修改,必须通过一个方法 setState
            this.setState({
                count: this.state.count + 1
            })
        }
        render () {
            // 2. 使用状态
            return (
            	<button onClick={this.changCount}>计数{this.state.count}</button>
            )
        }
    }
    

注意:

  1. 编写组件其实就是编写原生 js 类或者函数。
  2. 定义状态必须通过 state ,提供一个对象,名称固定叫做 state。
  3. 修改 state 中的任何属性,都不可以通过直接赋值,必须走 setState 方法。
  4. 关注 this 指向。

3.4 React 状态不可变

**概念:**不要直接修改状态的值,而是基于当前状态创建新的状态值

基于当前状态创建新值

state = {
    count: 0,
    list: [1,2,3],
    person: {
        name: 'owei',
        age: 18
    }
}


this.setState({
    count: this.state.count + 1,
    list: [...this.state.list,4],
    person: {
        ...this.state.person,
        // 覆盖原来的属性,就能修改对象中的属性值!!!
        name: 'rose'
    }
})

3.5 this 问题说明

image-20220607101726710

image-20220607102907360

image-20220607103414351

​ 上图中 this 沿用父函数(render)中的 this 指向。

3.6 表单处理

使用 React 处理表单元素,一般有两种方式:

  1. 受控组件(推荐)
  2. 非受控组件

3.6.1 受控表单组件(√)

受控组件

可以被 React 的状态控制 的组件。

React 组件的状态在 state 中, input 表单元素也有自己的状态是在 value 中,React 将state 与表单元素的值(value)绑定到一起, 由 state 的值来控制表单元素的值,从而保证单一数据源特性。

实现步骤:

以获取文本框的值为例,受控组件的使用步骤如下:

  1. 在组件的 state 中声明一个组件的状态数据。
  2. 将状态数据设置为 input 标签元素的 value 属性的值。
  3. 为 input 添加 change 事件。
  4. 在事件处理程序中,通过事件对象 e 获取到当前文本框的值(即当前用户的输入值)
  5. 调用 setState 方法,将文本框的值作为 state状态的更新值。

代码落地(双向绑定的底层写法)

import React from "react"

class Counter extends React.Component {
    // 1. 声明用来控制 input value 的react组件自己的状态
    state = {
        message: 'this is message'
    }
	// 回调函数
	inputChange = (e) => {
        // 4.拿到输入框最新的值,交给 state 中的 message
        this.setState({
            message: e.target.value
        })
    }
    // 产出 UI 模板结构
    render () {
        return (
            // 2.给 input 框的value属性绑定 react state
            // 3.给 input 框绑定一个 change 的事件,为了拿到当前输入框中的数据
        	<input
            	type='text'
            	value={this.state.message}
				onChange={this.inputChange}            
            />
        )
    }
}

function App () {
    return (
    	<div className = "App">
        	<InputComponent />
        </div>
    )
}

export default App

3.6.2 非受控表单组件

非受控组件

非受控组件就是通过手动操作 dom 的方式获取文本框的值,文本框的状态不受 react 组件的 state 中的状态的控制,直接通过原生 dom 获取输入框的值。

实现步骤

  1. 导入 createRef 函数
  2. 调用 createRef 函数,创建一个 ref 对象,存储到名为 msgRef 的实例属性中。
  3. 为 input 添加 ref 属性,值为 msgRef
  4. 在按钮的事件处理程序中,通过 msgRef.current 即可拿到对应的 dom 元素,而其中 msgRef.current.value 拿到的就是文本框的值。

代码实现

import React, {createRef} from 'react'

class Input extends React.Component {
    msgRef = createRef()
	getValue = () => {
        console.log(this.msgRef.current.value)
    }
	render () {
        <>
            <input
        		type='text'
        		ref={this.msgRef}
        	/>
            <button onClick={this.getValue}>点击获取输入框的值</button>
        </>
    }
}

额外补充小知识点:

​ 生成独一无二的 id 可以使用 uuid 包(yarn add uuid

import { v4 as uuid } from 'uuid'
uuid() // 得到一个独一无二的id

4 React 组件通信

组件通信的意义

​ 组件是独立且封闭的单元,默认情况下组件只能使用自己的数据(state)。组件化开发的过程中,完整的功能会拆分多个组件,在这个过程中不可避免的需要互相传递一些数据。为了让各组件之间可以进行互相沟通,数据传递,这个过程就是组件通信。

  1. 父子关系(√)

  2. 兄弟关系 - 自定义事件模式产生计数方法 eventBus / 通过共同的父组件通信

  3. 其他关系 - mobx/Redux/ 基于hook的方案

4.1 父传子实现

实现步骤

  1. 父组件提供要传递的数据 state

    // 父组件的数据
    state = {
        message: 'this is message'
    }
    
  2. 给子组件标签 -添加属性值为state的数据。

    // 子组件
    // 子组件身上绑定属性,属性名可以自定义,保持语义化
    <SonF msg={this.state.message} />
    
  3. 子组件中通过 props接收父组件中传过来的数据。

    1. 类组件使用 this.props获取props对象。
    2. 函数式组件直接通过参数获取props对象。

代码实现

// App 父组件           Son 子组件
import './App.css'
import React from 'react'
function SonF (props) {
  return (
    <div>
      <span>{props.msg}</span>
    </div>
  )
}

class SonG extends React.Component {
  render () {
    return (
      <div>{this.props.says}</div>
    )
  }
}

class App extends React.Component {
  state = {
    msg: '今天天气真好!',
    say: 'SonG: 确实不错!'
  }
  render () {
    return (
      <div>
        <p>父组件出现</p>
        <SonF msg={this.state.msg} />
        <SonG says={this.state.say}></SonG>
      </div>
    )
  }
}

export default App

4.2 props 说明

1. props 是只读对象(readonly)

根据单项数据流的要求,子组件只能读取 props 中的数据,不能进行修改。

2. props 可以传递任意数据

数字、字符串、布尔值、数组、对象、函数、JSX(vue中传递模板使用插槽)

// App 父组件           Son 子组件
import './App.css'
import React from 'react'
function SonF (props) {
  return (
    <div>
      {props.list.map(item => <p key={item}>{item}</p>)}
      {props.userInfo.name}
	  <button onClick={props.getMes}>触发传入的函数</button>
	  {props.child}
    </div>
  )
}

class App extends React.Component {
    state = {
        list: [1,2,3],
        userInfo: {
            name: 'owei',
            age: 59
        },
    }
    render () {
    	return (
      	<div>
        	<SonF 
            	list={this.state.list} 
				userInfo={this.state.name}
				getMes={()=>{console.log("父组件的函数")}}
                child={<p>jsx 模板</p>}
			/>
      	</div>
    	)
 	 }
}

4.3 porps 解构赋值

  1. 对props进行解构
  2. 在参数处直接解构

porps 其实就是一个普通的 js 对象!

实现代码

import React from 'react'
// App 父组件;Son 子组件
// 函数式的Son
function SonF (props) {
    // props 是一个对象,里面存着通过父组件传入的所有数据
    console.log(props)
    // 解构赋值
    const { list,userInfo,getMes,child } = props
    return (
    	<div>
        {list.map(item => <p key={item}>{item}</p>)}
      	{userInfo.name}
	  	<button onClick={getMes}>触发传入的函数</button>
	  	{child}	
        </div>
    )
}
// 或者在参数里解构赋值
function SonG ({ list, userInfo, getMes, child }) {
    // props 是一个对象,里面存着通过父组件传入的所有数据
    console.log(props)
    return (
    	<div>
        {list.map(item => <p key={item}>{item}</p>)}
      	{userInfo.name}
	  	<button onClick={getMes}>触发传入的函数</button>
	  	{child}	
        </div>
    )
}

class App extends React.Component {
    state = {
        list: [1,2,3],
        userInfo: {
            name: 'owei',
            age: 59
        },
    }
    render () {
    	return (
      	<div>
        	<SonF 
            	list={this.state.list} 
				userInfo={this.state.name}
				getMes={()=>{console.log("父组件的函数")}}
                child={<p>jsx 模板</p>}
			/>
            <SonG
            	list={this.state.list} 
				userInfo={this.state.name}
				getMes={()=>{console.log("父组件的函数")}}
                child={<p>jsx 模板</p>}
			/>
      	</div>
    	)
 	 }
}

4.4 子传父

子组件调用父组件传递过来的函数,并且把想要传递的数据当成函数的实参传入即可。

代码实现

function Son (props) {
    const { getSonMsg } = props
    return (
    	<div>
        	子组件
        	<button onClick= {()=>getSonMsg('这是来自于子组件的参数')}></button>
        </div>
    )
}

class App extends React.Component {
    // 准备数据
    state = {
        list: [1,2,3]
    }
	// 1.准备一个函数,传个子组件
	getSonMsg = (sonMsg) => {
        console.log(sonMsg)
    }
    
    render () {
        return (
        	<div>
            	<Son getSonMsg={this.getSonMsg />
            </div>
        )
    }
}

4.5 兄弟组件通信

通过状态提升机制,利用共同的父组件实现兄弟通信。

image-20220611102135079

实现步骤

// 实现思路
// 先将SonB中的数据通过子传父,传给App
// 再把App接收到的SonB中的数据,通过父传子传递给SonA
function SonA (props) {
    return (
    	<div>this is SonA
        	<p>A组件{props.aMsg}</p>
        </div>
   	)
}

function SonB (props) {
    const bMsg = "SOnB中的数据"
    return (
    	<div>this is SonB
        	<button onClick={()=>props.getBMsg(bMsg)}>clickB</button>
        </div>
   	)
}

class App extends React.Component {
    state = {
        message: ''
    }
    // 函数传给SonB
    getBMsg = (msg) => {
        console.log(msg)
        // 将获取的SonB的传值,更新到App中
        this.setState({
            message: msg
        })
    }
    render () {
        <div>
            <SonA aMsg=this.state.message/>
            <SonB getBMsg={this.getBMsg}/>
        </div>
    }
}

4.6 跨组件通信 Context

背景介绍

image-20220611104056713

如上图所示是一个 react 形成的嵌套组件树,如果我们想从 App 组件向任意一个下层组件传递数据,可以怎么做?

  1. 采用一层一层的props往下传,层级较多时非常麻烦。
  2. Context 提供了一个无需为每层组件手动添加 props, 就能在组件树间进行数据传递的方法。

实现步骤

  1. 创建 Context 对象,导出 Provider 和 Consumer 对象

    const { Provider,Consumer} = createContext()
    
  2. 使用 Provider 包裹根组件提供数据

    <Provider value = {this.state.message}>
    	{/* 根组件 */}
    </Provider>
    
  3. 需要用到数据的组件使用 Consumer 包裹获取数据

    <Consumer>
        {value => /* 基于 context 值进行渲染*/}
    </Consumer>
    

注意事项

  1. 上层组件和下层组件关系是相对的,只要存在上下层关系就可以使用。通过会通过 App 作为数据提供方。

  2. 涉及到的语法都是固定的(提供数据的位置必须用value,或许数据的位置{value =>{}}

代码实现

// App 组件直接包含 SonA, SonA 组件直接包含 SonC
// 需求: App 直接将数据传给 SonC
import React, { createContext } from 'react'

// 1. 导入 createContext 方法
const { Provider, Consumber } = createContext
function SonA () {
    return (
    	<div>
        	this is SonA
        	<SonC/>
        </div>
    )
}

function SonC () {
    return (
    	<div> 
        	this is SonC
        	// 3. 消费数据
        	<Consumer>
        		{value=><span>{value}</span>}
        	</Consumer>
        </div>
    )
}

class App extends React.Component {
    state = {
        message: '父组件的值'
    }
    render () {
        return (
            // 2. 使用 Provider 包裹根组件
            <Provider value = { this.state.message }>
            	<div>
            		this is App
            		<SonA/>
            	</div>
            </Provider>
        )
    }
}

5 组件(进阶)

5.1 children 属性

props 中 children 属性的用法

children 属性表示该组件的子节点,只要组件内部有子节点,props 中就有该属性。children 可以是==普通文本、普通标签元素、函数、JSX。==如果 children 中有多个元素,以数组形式展示。

实现效果

import React from 'react'
// 渲染列表
function ListItem ({ children}) {
    return (
    	<div>
        	ListItem
        	<p>{children}</p>
        
        </div>
    )
}

class App extends React.Component {
    
    render () {
        return (
        	<div>
            	<ListItem>
            	// 在 ListItem节点中写入“this is child”,props中就有该“this is child"
            		this is child
            	</ListItem>
            </div>
        )
    }
}

image-20220611161422660

5.2 props 校验-常见和使用

对于组件来说,props 是由外部传入的,我们无法保证组件使用者传入了什么格式的数据,如果传入的数据格式不对,就有可能会导致组件内部错误,组件的使用者可能报错了也不知道具体原因

例如:需要对colors 进行 prop 校验

image-20220611163108619

实现步骤(React 中并不是内置的 prop 校验)

  1. 安装属性校验包yarn add prop-types
  2. 导入 prop-types包。
  3. 使用组件名.propTypes = {}给组件添加校验规则。

实现代码

// 注意其中的大小写!
import React from 'react'
// porp-types 含有各种各样的内置校验规则
import PropTypes from 'prop-types'

function Test ({ list }) {
    return (
    	<div>
        	{ list.map(item => <p>{item}</p>)}
        </div>
    )
}

Test.propTypes = {
    // 定义规则
    list: PropTypes.array //  限定这里的 list 参数类型必须是数组类型
}

class App extends React.Component {
    render () {
        return (
        	<div>
            	<Test list={[1,2,3]} />
            </div>
        )
    }
}

5.3 类型校验说明

四种常见结构

  1. 常见类型: array、bool、func、number、object、string

  2. React 元素类型: element(jsx)

  3. 必填项: isRequired

    Test.propTypes = {
        list: PropTypes.array.isRequired // 限定这里的list参数类型必须是数组,且是必填
    }
    
  4. 特定的结构对象: shape({})

核心代码

// 常见类型
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape:  PropTypes.shape({
	color: PropTypes.string,
	fontSize: PropTypes.number
})

5.4 校验中的默认值

通过 defaultProps 可以给组件的 props 设置默认值,在未传入 props 的时候生效。

5.4.1 函数组件

两种传递方式的区别:第一种在用的时候组件内部已经有了 pageSize 这个 prop;第二种只有传递的时候组件内部才有这个 prop。

  • 使用 defaultProps
function List(props) {
    return (
    	<div>
        	此处展示props的默认值: {props.pageSize}
        </div>
    )
}
// 设置默认值
List.defaultProps = {
    pageSize: 10
}
// 不传入pageSize属性
<List />
  • 使用函数参数默认值(推荐

注:函数组件,新版的 react 已经不再推荐使用 defaultProps 来添加默认值,而是推荐 函数参数默认值来实现。

function List ({pageSize = 10}) {
    return (
    	<div>
        	此处展示 props 的默认值: { pageSize }
        </div>
    )
}

5.4.2 类组件

  • 使用defaultProps
class List extends React.Component {
    render () {
        return (
        	<div>
            	此处展示props的默认值: {this.props.pageSize}
            </div>
        )
    }
}
List.defaultProps = {
    pageSize: 10
}
// 不传入 pageSize 属性
<List />
  • 使用类静态属性声明(推荐)
class List extends React.Component {
    static defaultProps = {
        pageSize: 10
    }
	
	render () {
        return (
        	<div>
            	此处展示 props 的默认值:{this.props.pageSize}
            </div>
        )
    }
}

6 组件生命周期

6.1 生命周期概述

组件的生命周期是指组件从被创建到挂载到页面中运行起来,再到组件不用时卸载的过程,注意,只有类组件才有生命周期。(类组件需要实例化,存在生命周期;函数组件,不需要实例化没有生命周期。

不可以在 render/componentDidUpdate 中执行setState.

图例:下图

image-20220611223540111

6.2 挂载阶段

执行顺序钩子函数触发时机作用
1constructor创建组件时,最先执行,初始化的时候只执行一次1.初始化state。2.创建Ref。3.使用bind解决this指向问题等。
2render每次组件渲染都会触发(视图变化就会执行)渲染UI(注意: 不能在里面调用 setState())
3componentDidMount组件挂载(完成DOM渲染)后执行,初始化的时候执行一次1.发送网络请求。2.DOM操作。

6.3 更新阶段

执行顺序钩子函数触发时机作用
1render每次组件渲染都会触发渲染UI(与挂载阶段是同一个render)
2componentDidUpdate组件更新后(DOM渲染完毕)DOM操作,可以获取到更新后的DOM内容,不要直接调用setState

6.4 卸载阶段

钩子函数触发时机作用
componentWillUnmount组件卸载(从页面中消失)执行清理工作(比如:清理定时器等)

7 Hooks

纯函数(pure function)

给一个函数同样的参数,那么这个函数永远返回同样的值。即React组件输入相同的参数(props),渲染UI应该永远一样。

7.1 概念

Hooks 的本质: 一套能使函数组件更强大,更灵活的 ”钩子“

某种意义上 hook 的出现,就是想不用生命周期概念也可以写业务代码。

React 体系里组件分为 类组件函数组件

​ 函数组件是一个更加匹配 React 的设计理念==UI=f(data) ==, 也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不具有自己状态的,为了能让函数组件可以拥有自己的状态,所以 react v16.8开始,Hooks 应运而生。

注意点:

  1. 有了 hooks 之后,为了兼容老版本,class 类组件并没有被移除,两者都可以使用。
  2. 有了hooks 之后,不能再把函数当成无状态组件了,因为 hooks 为函数组件提供了状态。
  3. hooks 只能在函数组件中使用

Hooks 的出现解决了两个问题:

  • 组件的状态逻辑复用。

    在 hooks 出现之前,react 先后尝试了 mixins 混入,HOC 高阶组件, render-props等模式。但是都有各自的问题,比如 mixin 的数据来源不清晰,高阶组件的嵌套问题等等。

  • class 组件自身的问题。

    class 组件大而全,提供了许多内容,有不可忽视的学习成本,比如各种生命周期,this 指向问题等等。

Hooks 优点

  • 告别难以理解的 class
  • 解决业务逻辑难以拆分的问题。
  • 使状态逻辑复用变得简单可行。
  • 函数组件在设计思想上,更加契合 React 的理念。

7.2 useState

状态钩子:useState(),React自带的hook函数,声明组件状态。其返回值为包含count,setCount的数组,分别表示[状态,状态更新函数],userState(0)是初始count(state)值为0。

7.2.1 代码实现

// 累加运算 useState
// 快速使用
// 1. 导入 useState函数 react
// 2.执行这个函数并且传入初始值,必须在函数组件中。
// 3.[数据,修改数据的方法]
// 4.使用数据,修改数据

import React, {useState} from 'react'
function App () {
	const [count, setCount] = useState(0)
    return (
    	<div>
        	<button onClick={() => setCount(count + 1)}>{count}</button>
        </div>
    )
   
}
export default App

7.2.2 状态的读取和修改

// 状态的读取和修改
// 数组的解构赋值,两者的顺序不可以交换(第一个参数就是数据状态,第二个参数就是修改数据的方法)
const [count, setCount] = useState(0)
// 1.useState 传过来的参数,作为 count 的初始值。
// 2.useState 返回值是一个数组
// 3.setCount 函数,用来修改count,依旧保持不能直接修改原值,而是生成一个新值替换原值。
// setCount(基于原值计算得到的新值)
// 4.count 和 setCount 是一对,setCount只能用来修改对应的count值。

7.2.3 组件的更新过程

​ 函数组件使用 useState hook 之后的执行过程,以及状态值的变化

  • 组件第一次渲染(首次渲染)
    1. 从头开始执行该组件中的代码逻辑
    2. 调用 useState(0)将传入的参数作为状态初始值,即: 0
    3. 渲染组件,此时,获取的状态 count 值为: 0
  • 组件第二次渲染(更新渲染)
    1. 点击按钮,调用setCount(count+1)修改状态,因为状态发生改变,所以,该组件会重新渲染。
    2. 组件重新渲染时,会再次执行该组件中的代码逻辑。
    3. 再次调用 useState(0),此时 React 内部会拿到最新的状态值而非初始值,比如,上述代码中最新的状态值为1。
    4. 再次渲染组件,此时,获取到状态count 值为: 1

useState的初始值(参数)只会在组件第一次渲染时生效。以后的每次渲染,useState获取到的都是最新的状态值, React 组件会记住每次最新的状态值。

7.2.4 使用规则

  1. useState函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态。

    function List() {
        // 以字符串为初始值
        const [name,setName] = useState('cp00')
        // 以数组为初始值
        const [list,setList] = useState([])
    }
    
  2. useState注意事项

    • 只能出现在函数组件中。

    • 不能嵌套在 if/for/其他函数中 (react 按照 hooks 的调用顺序标识每一个 hook)

      let num = 1
      function List() {
          num++
          if (num/2 === 0) {
              const [name,setName] = useState('cp00')
          }
          const [list,setList] = useState([])
      }
      // 两个hook顺序不固定,不可行!!
      
    • 可以通过开发者工具查看 hooks 状态。

7.3 useEffect

7.3.1 函数副作用

什么是副作用

​ 副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用是根据数据 (state/props) 渲染 UI, 除此之外都是副作用(比如,手动修改DOM)

常见的副作用

  1. 数据请求 ajax 发送。

  2. 手动修改DOM

  3. localstorage 操作

    useEffect 函数的作用就是为了 react 函数组件提供副作用处理的!

7.3.2 基础使用

image-20220612101149044

7.3.3 依赖项控制执行时机

1.不添加依赖项

组件首次渲染执行一次,以及不管是那一个状态更改引起组件更新时都会重新执行。

  • 组件初始渲染
  • 组件更新(不管是哪个状态引起的更新)
useEffect(() => {
    console.log('副作用执行了')
})

2.添加空数组

组价只在首次渲染时执行一次(再次更新不执行)

useEffect(() => {
    console.log('副作用执行了')
},[])

3.添加特定依赖项

副作用函数在首次渲染时执行,在依赖项发生变化时重新执行。

function App() {
    const [count,setCount] = useState(0)
    const [name,setName] = useState('zs')
    
    useEffect(() => {
    	console.log('副作用执行了')
        // useEffect回调函数中用到的数据状态应该出现在依赖项数组声明中,避免产生问题(数据更新了,内容没有重新渲染)
        document.title = count
        console.log(name)
	},[count,name]) 
}

image-20220612102355658

image-20220612102331458

7.4 自定义hook

需求:自定义一个hook函数,实现获取滚动距离Y

const[y]=useWindowScroll() y即滚动到顶部的距离

import { useState } from 'react'
export function useWindowScroll () {
    const [y, sety] = useState(0)
    // 在滚动行为发生的时候,不断获取滚动值,然后交给y
    window.addEventListener('scroll',() => {
        const h = document.documentElement.scrollTop
        sety(h)
    })
    return [y]
}

import { useWindowScroll } from './hooks/useWindowScroll'
function App () {
    const [y] = useWindowScroll()
    return (
    	<div style = {{height:'12000px'}}>
        	{y}
        </div>
    )
}
export default App

需求: 自定义hook函数,可以自动同步到本地LocalStorage

const [message,setMessage] = useLocalStorage(defaultValue)

  • message 可以通过自定义传入默认初始值
  • 每次修改message数据的时候,都会自动往本地同步一份
import { useState,useEffect } from 'react'
export function useLocalStorage(key,defaultValue) {
    const [message,setMessage] = useState(defaultValue)
    // 每次只要message变化,就会自动同步到本地LocalStorage
    // 副作用本地存储
    useEffect(() => {
        window.localStorage.setItem(key,message)
    },[message,key])
    return [message,setMessage]
}

import { useLocalStorage } from './hooks/useLocalStorage'
function App () {
    const [message,setMessage] = useLocalStorage('hook-key','阿飞')
    setTimeout(() => {
        setMessage('cp')
    }, 5000)
    return (
    	<div style = {{height:'12000px'}}>
        	{y}
        </div>
    )
}
export default App

8 Hooks 进阶

8.1 useState-回调函数的参数

(useState 回调函数作为参数的使用场景)

实质上就是通过函数获取初始值state

使用场景

​ 参数只会在数组的初始渲染中起作用,后续渲染时会被忽略。如果初始state需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染时被调用

语法

const [name,setName] = useState(() => {
    // 初始state需要计算获得
    // 编写计算逻辑
})

语法规则

  1. 回调函数 return 出去的值将作为 name 的初始值。
  2. 回调函数中的逻辑只会在组件初始化的时候执行一次。

语法选择

  1. 如果就是初始化一个普通的数据,直接使用useState(普通数据)即可
  2. 如果初始化的数据无法直接得到需要通过计算才能获取到,使用 useState(() => {})

需求(练手)

image-20220618174238171

import { useState }  from 'react'

function getDefaultValue () {
    for (let i = 0;i < 1000; i++) {
        
    }
    return '10'
}
function Counter (props) {
    const [count, setCount] = useState(() => {
        // 只要无法直接确定,需要通过一定的操作才能获得,就可以理解为计算
        //return props.count
        return getDefaultValue()
    })
    return (
    	<button onClick = {() => setCount(count + 1)}>{count}</button>
    )
}

function App () {
    return (
    	<div>
        	<Counter count={10} />
			<Counter count={10} />
        </div>
    )
}

8.2 useEffect 清理副作用

(清理useEffect 方法)

使用场景

​ 在组件被销毁时,如果有些副作用操作需要被清理,就可以使用此语法,比如常见的定时器。

语法及规则

useEffect(() => {
    console.log('副作用函数执行了')
    // 副作用函数的执行时机为:在下一次副作用函数执行之前执行
    return () => {
        console.log('清理副作用的函数执行了')
        // 在这里写清理副作用的代码
    }
})

// eg
function Test () {
    useEffect(() => {
        let timer = setInterval(() => {
            console.log('定时器执行了')
        },1000)
        return () => {
            clearInterval(timer)
        }
    },[])
    return (
    	<div>this is test component!
    )
}

8.3 useEffect 发送网络请求

类组件发送网络请求:componentDidMount(组件挂载完成,即初始化时dom渲染完毕,只执行一次)。类似于hook中的useEffect (fun,[])

一个组件内部可以有多个useEffect,它们之间相互独立互不影响

使用场景

​ 如何在 useEffect 中发送网络请求,并且封装同步 async await 操作

语法要求

​ 不可以直接在useEffect 的回调函数外层直接包裹 await ,因为 异步会导致清理函数无法立即返回

// 错误示例
useEffect(async () => {
    const res = await axios.get('http://geek.itheima....')
    console.log(res)
},[])

正确写法

​ 在内部单独定义一个函数,然后把这个函数包装成同步

useEffect(() => {
    async function fetchData() {
        const res = await axios.get('http://geek.itheima.net/v1_0/channels')
        console.log(res)
    }
    fetchData()
},[])

8.4 useRef

使用场景

在函数组件中获取真实的 dom 元素对象或者是组件对象。

使用步骤

  1. 导入useRef函数。
  2. 执行useRef函数并传入null,返回值为一个对象,对象内部有一个 current 属性存放拿到的 dom 对象(组件实例)
  3. 通过 ref 绑定要获取的元素或者组件。

获取Dom

import {useEffect, useRef} from 'react'
function App () {
    const h1Ref = useRef(null)
    useEffect(() => {
        console.log(h1Ref)
    },[])
    return (
    	<div>
        	<h1 ref={h1Ref}>thi is h1</h1>
        </div>
    )
}
export default App

获取组件实例

函数组件由于没有实例,不能使用 ref 获取,如果想获取组件实例,必须是类组件。

8.5 useContext

补充

Context 如果要传递数据,只需要在整个应用初始化的时候传递一次就可以,就可以选择在index.js 文件中做数据提供。(静态的)

如果 Context 需要传递数据并且将来还需要在对数据做修改,底层组件也需要数据同步改变,可以选择在app.js中做数据提供。(动态的

实现步骤

  1. 使用 createContext 创建 Context 对象
  2. 在顶层组件通过 Provider提供数据(可以是App.js,也可以是index.js)
  3. 在底层组件通过useContext(Context)函数获取数据

代码实现

import { createContext, useContext } from 'react'
// 创建Context对象
const Context = createContext()

function Foo() {
    return <div>Foo<Bar/><div>
}

function Bar() {
    // 底层组件通过useContext函数获取数据
    const name = useContext(Context)
    return <div>Bar {name}</div>
}

function App() {
    return {
        // 顶层组件通过 Provider 提供数据
        // value 数据更新,则使用value值的其他组件数据也会更新
        <Context.Provider value={'this is name'}>
        	<div>
        		<Foo/>
       		</div>
        </Context.Provider>
    }
}
export default App

补充

// 1.调用createContext 方法
// 2. 通过顶层组件包裹一下,Context.Provider
// 3.底层组件 useContext(createContext 返回的对象)
import { createContext } from 'react'
const Context = createContext()
export default Context
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值