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 特点
-
声明式UI(jSX)
写UI就和写普通HTML一样,抛弃命令式的繁琐实现,不需要逐步编写dom逻辑去实现。
-
组件化
组件是React中最重要的内容,组件可以通过搭积木的方式拼成一个完整的页面,通过组件的抽象可以增加复用能力和提高可维护性。
-
一次学习,跨平台编写
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 格式化配置
-
安装VSCode prettier 插件
-
修改配置文件
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>
可以使用的表达式
- 字符串、数值、布尔值、null、undefined、object
- 加减乘除运算、
- 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 组件概念
上图所示的左侧页面效果对应到React中,它是一个组件树的形式。
3.1.1 函数组件
使用 JS 的函数(或箭头函数)创建的组件,就叫做
函数组件
。
// 函数组件的创建和渲染
// 创建
function Hello () {
return <div>hello</div>
}
// 渲染 <Hello/>
// 渲染 <Hello></Hello>
function App () {
return (
<div>
<Hello/>
</div>
)
}
export default App
约定:
- 组件的名称
必须首字母大写
,react 内部会根据这个来判断是组件还是普通HTML标签。 - 函数组件
必须有返回值
,表示该组件的 UI 结构;如果不需要渲染任何内容,则返回null。 - 组件就像 HTML 标签一样可以被渲染到页面中。组件表示的是一段结构内容,对于函数组件来说,渲染的内容是组件的
返回值
就是对应的内容。 - 使用函数名称作为组件标签名称,可以成对出现也可以自闭合。
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
约定:
- 类名称必须首字母大写。
- 类组件应该继承 React.Component 父类, 从而使用父类中提供的方法或属性。
- 类组件必须提供 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> ) } }
注意:
- 编写组件其实就是编写原生 js 类或者函数。
- 定义状态必须通过 state ,提供一个对象,名称固定叫做 state。
- 修改 state 中的任何属性,都不可以通过直接赋值,必须走 setState 方法。
- 关注 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 问题说明
上图中 this 沿用父函数(render)中的 this 指向。
3.6 表单处理
使用 React 处理表单元素,一般有两种方式:
- 受控组件(推荐)
- 非受控组件
3.6.1 受控表单组件(√)
受控组件
可以
被 React 的状态控制
的组件。React 组件的状态在 state 中, input 表单元素也有自己的状态是在 value 中,React 将state 与表单元素的值(value)绑定到一起, 由 state 的值来控制表单元素的值,从而保证单一数据源特性。
实现步骤:
以获取文本框的值为例,受控组件的使用步骤如下:
- 在组件的 state 中声明一个组件的状态数据。
- 将状态数据设置为 input 标签元素的 value 属性的值。
- 为 input 添加 change 事件。
- 在事件处理程序中,通过事件对象 e 获取到当前文本框的值(即当前用户的输入值)
- 调用 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 获取输入框的值。
实现步骤
- 导入 createRef 函数
- 调用 createRef 函数,创建一个 ref 对象,存储到名为 msgRef 的实例属性中。
- 为 input 添加 ref 属性,值为 msgRef
- 在按钮的事件处理程序中,通过 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)。组件化开发的过程中,完整的功能会拆分多个组件,在这个过程中不可避免的需要互相传递一些数据。为了让各组件之间可以进行互相沟通,数据传递,这个过程就是组件通信。
-
父子关系(√)
-
兄弟关系 - 自定义事件模式产生计数方法 eventBus / 通过共同的父组件通信
-
其他关系 - mobx/Redux/ 基于hook的方案
4.1 父传子实现
实现步骤
-
父组件提供要传递的数据
state
// 父组件的数据 state = { message: 'this is message' }
-
给子组件标签 -
添加属性
值为state的数据。// 子组件 // 子组件身上绑定属性,属性名可以自定义,保持语义化 <SonF msg={this.state.message} />
-
子组件中通过
props
接收父组件中传过来的数据。- 类组件使用
this.props
获取props
对象。 - 函数式组件直接通过参数获取
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 解构赋值
- 对props进行解构
- 在参数处直接解构
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 兄弟组件通信
通过状态提升机制,利用共同的父组件实现兄弟通信。
实现步骤
// 实现思路
// 先将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
背景介绍
如上图所示是一个 react 形成的嵌套组件树,如果我们想从 App 组件向任意一个下层组件传递数据,可以怎么做?
- 采用一层一层的props往下传,层级较多时非常麻烦。
- Context 提供了一个无需为每层组件手动添加 props, 就能在组件树间进行数据传递的方法。
实现步骤
-
创建 Context 对象,导出 Provider 和 Consumer 对象
const { Provider,Consumer} = createContext()
-
使用 Provider 包裹根组件提供数据
<Provider value = {this.state.message}> {/* 根组件 */} </Provider>
-
需要用到数据的组件使用 Consumer 包裹获取数据
<Consumer> {value => /* 基于 context 值进行渲染*/} </Consumer>
注意事项
-
上层组件和下层组件关系是相对的,只要存在上下层关系就可以使用。通过会通过 App 作为数据提供方。
-
涉及到的语法都是固定的(提供数据的位置必须用
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>
)
}
}
5.2 props 校验-常见和使用
对于组件来说,props 是由外部传入的,我们无法保证组件使用者传入了什么格式的数据,如果传入的数据格式不对,就有可能会导致组件内部错误,组件的使用者可能报错了也不知道具体原因。
例如:需要对colors 进行 prop 校验
实现步骤(React 中并不是内置的 prop 校验)
- 安装属性校验包:
yarn add prop-types
。 - 导入
prop-types
包。 - 使用
组件名.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 类型校验说明
四种常见结构
-
常见类型: array、bool、func、number、object、string
-
React 元素类型: element(jsx)
-
必填项: isRequired
Test.propTypes = { list: PropTypes.array.isRequired // 限定这里的list参数类型必须是数组,且是必填 }
-
特定的结构对象: 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.
图例:下图
6.2 挂载阶段
执行顺序 | 钩子函数 | 触发时机 | 作用 |
---|---|---|---|
1 | constructor | 创建组件时,最先执行,初始化的时候只执行一次 | 1.初始化state。2.创建Ref。3.使用bind解决this指向问题等。 |
2 | render | 每次组件渲染都会触发(视图变化就会执行) | 渲染UI(注意: 不能在里面调用 setState()) |
3 | componentDidMount | 组件挂载(完成DOM渲染)后执行,初始化的时候执行一次 | 1.发送网络请求。2.DOM操作。 |
6.3 更新阶段
执行顺序 | 钩子函数 | 触发时机 | 作用 |
---|---|---|---|
1 | render | 每次组件渲染都会触发 | 渲染UI(与挂载阶段是同一个render) |
2 | componentDidUpdate | 组件更新后(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 应运而生。
注意点:
- 有了 hooks 之后,为了兼容老版本,class 类组件并没有被移除,两者都可以使用。
- 有了hooks 之后,不能再把函数当成无状态组件了,因为 hooks 为函数组件提供了状态。
- 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 之后的执行过程,以及状态值的变化
- 组件第一次渲染(首次渲染)
- 从头开始执行该组件中的代码逻辑
- 调用
useState(0)
将传入的参数作为状态初始值,即: 0 - 渲染组件,此时,获取的状态 count 值为: 0
- 组件第二次渲染(更新渲染)
- 点击按钮,调用
setCount(count+1)
修改状态,因为状态发生改变,所以,该组件会重新渲染。 - 组件重新渲染时,会再次执行该组件中的代码逻辑。
- 再次调用
useState(0)
,此时 React 内部会拿到最新的状态值而非初始值,比如,上述代码中最新的状态值为1。 - 再次渲染组件,此时,获取到状态count 值为: 1
- 点击按钮,调用
注:useState
的初始值(参数)只会在组件第一次渲染时生效。以后的每次渲染,useState
获取到的都是最新的状态值, React 组件会记住每次最新的状态值。
7.2.4 使用规则
-
useState
函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态。function List() { // 以字符串为初始值 const [name,setName] = useState('cp00') // 以数组为初始值 const [list,setList] = useState([]) }
-
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)
常见的副作用
-
数据请求 ajax 发送。
-
手动修改DOM
-
localstorage 操作
useEffect 函数的作用就是为了 react 函数组件提供副作用处理的!
7.3.2 基础使用
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])
}
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需要计算获得
// 编写计算逻辑
})
语法规则
- 回调函数 return 出去的值将作为
name
的初始值。 - 回调函数中的逻辑只会在组件初始化的时候执行一次。
语法选择
- 如果就是初始化一个普通的数据,直接使用useState(普通数据)即可
- 如果初始化的数据无法直接得到需要通过计算才能获取到,使用
useState(() => {})
需求(练手)
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 元素对象或者是组件对象。
使用步骤
- 导入
useRef
函数。 - 执行
useRef
函数并传入null,返回值为一个对象,对象内部有一个 current 属性存放拿到的 dom 对象(组件实例) - 通过 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中做数据提供。(动态的)
实现步骤
- 使用
createContext
创建 Context 对象 - 在顶层组件通过
Provider
提供数据(可以是App.js,也可以是index.js) - 在底层组件通过
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