react 学习笔记
1. react概念
React是什么
一个专注于构建用户界面的 JavaScript 库,和vue和angular并称前端三大框架,不夸张的说,react引领了很多新思想,世界范围内是最流行的js前端框架,最近发布了18版本,加入了很多新特性
React英文文档(reactjs.org/)
React中文文档 (zh-hans.reactjs.org/)
React新文档 (beta.reactjs.org/)(开发中....)
React有什么特点
1- 声明式UI(JSX)
写UI就和写普通的HTML一样,抛弃命令式的繁琐实现
const VDOM = <h1>Hello React</h1>
2- 组件化
组件是react中最重要的内容,组件可以通过搭积木的方式拼成一个完整的页面,通过组件的抽象可以增加复用能力和提高可维护性
3- 跨平台
react既可以开发web应用也可以使用同样的语法开发原生应用(react-native),比如安卓和ios应用,甚至可以使用react开发VR应用,想象力空间十足,react更像是一个 元框架 为各种领域赋能
2. 创建React项目
-
在网站中添加 React
-
引入react核心库js文件个和babel文件
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> -
创建容器和渲染
<div id="root"></div>const root = ReactDOM.createRoot(document.querySelector('#test')) root.render(<Login></Login>)虚拟DOM创建
-
jsx方式
const root = ReactDOM.createRoot(document.querySelector("#test")); root.render(VDOM) -
js
//1.创建虚拟DOM const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React')) //2.渲染虚拟DOM到页面 ReactDOM.render(VDOM,document.getElementById('test'))
-
-
完整结构
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Learn React</title> </head> <body> <div id="test"></div> <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> // 1. 创建jsx结构 const VDOM = <h1>Hello React!!!</h1> //2.渲染虚拟DOM到页面 18版本写法 const root = ReactDOM.createRoot(document.querySelector('#test')) root.render(VDOM) // 低版本写法, // ReactDOM.render(VDOM, document.getElementById("test")); </script> </body> </html>
-
-
脚手架创建
npx create-react-app xxxx //注意项目名都只能小写项目目录说明调整
-
目录说明
-
-
src目录是我们写代码进行项目开发的目录 -
package.json中俩个核心库:react 、react-dom
-
-
目录调整
-
-
删除src目录下自带的所有文件,只保留app.js根组件和index.js
-
创建index.js文件作为项目的入口文件,在这个文件中书写react代码即可
-
-
入口文件说明
import React from 'react' import ReactDOM from 'react-dom' import './index.css' // 引入根组件App import App from './App' // 通过调用ReactDOM的render方法渲染App根组件到id为root的dom节点上 // React.StrictMode 是开启严格模式 ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') )
-
3. JSX 基础
1. JSX介绍
目标任务: 能够理解什么是JSX,JSX的底层是什么
概念:JSX是 JavaScript XML(HTML)的缩写,表示在 JS 代码中书写 HTML 结构
作用:在React中创建HTML结构(页面UI结构)
优势:
- 采用类似于HTML的语法,降低学习成本,会HTML就会JSX
- 充分利用JS自身的可编程能力创建HTML结构
注意:JSX 并不是标准的 JS 语法,是 JS 的语法扩展,浏览器默认是不识别的,脚手架中内置的 @babel/plugin-transform-react-jsx 包,用来解析该语法。
语法规则:
-
定义虚拟DOM时,不要写引号。
-
标签中混入JS表达式时要用 {} 。
-
样式的类名指定不要用class,要用className。
-
内联样式,要用style={{key:value}} 的形式去写。
-
只有一个根标签
-
标签必须闭合
-
标签首字母
(1). 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
(2). 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
区分表达式和语句
一定注意区分:【js语句(代码)】与【js表达式】
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式:
(1). a
(2). a+b
(3). demo(1)
(4). arr.map()
(5). function test () {}
2.语句(代码):
下面这些都是语句(代码):
(1).if(){}
(2).for(){}
(3).switch(){case:xxxx}
2. JSX使用js表达式
语法
{ JS 表达式 }
可以使用的表达式
- 字符串、数值、布尔值、null、undefined、object( [] / {} )
- 1 + 2、'abc'.split('')、['a', 'b'].join('-')
- fn()
3. JSX列表渲染
实现:使用map函数
// 来个列表
const songs = [
{ id: 1, name: '痴心绝对' },
{ id: 2, name: '像我这样的人' },
{ id: 3, name: '南山南' }
]
function App() {
return (
<div className="App">
<ul>
{
songs.map((item,inedx) => <li key={index}>{item.name}</li>)
}
</ul>
</div>
)
}
export default App
注意点:需要为遍历项添加key值,一般展示可以使用index,否则为了效率,最好使用唯一标识(id等)
- key 在 HTML 结构中是看不到的,是 React 内部用来进行性能优化时使用
- key 在当前列表中要唯一的字符串或者数值(String/Number)
- 如果列表中有像 id 这种的唯一值,就用 id 来作为 key 值
- 如果列表中没有像 id 这种的唯一值,就可以使用 index(下标)来作为 key 值
4. JSX条件渲染
目标任务: 能够在JSX中实现条件渲染
作用:根据是否满足条件生成HTML结构,比如Loading效果
实现:可以使用 三元运算符 或 逻辑与(&&)运算符
// 来个布尔值
const flag = true
function App() {
return (
<div className="App">
{/* 条件渲染字符串 */}
{flag ? 'react真有趣' : 'vue真有趣'}
{/* 条件渲染标签/组件 */}
{flag ? <span>this is span</span> : null}
</div>
)
}
export default App
5. JSX 样式
目标任务: 能够在JSX中实现css样式处理
-
行内样式 - style
<div style={{ color: 'red' }}>this is a div</div> -
行内样式 - style - 更优写法
const styleObj = { color:red } <div style={ styleObj }>this is a div</div> -
类名-className-动态类名控制
import './app.css' function App() { const [showTitle,setShowTitle] = useState(true) return ( <div className="App"> <div className={ showTitle ? 'title' : ''}>this is a div</div> </div> ) } export default App
4. 函数式组件和类组件
-
函数式组件
//1.创建函数式组件 function MyComponent(){ console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式 return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2> } //2.渲染组件到页面 ReactDOM.render(<MyComponent/>,document.getElementById('test'))执行了ReactDOM.render(.......之后,发生了什么?
- React解析组件标签,找到了MyComponent组件。
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
-
类组件
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
//构造器调用几次? ———— 1次
constructor(props){
console.log('constructor');
super(props)
//初始化状态
this.state = {isHot:false,wind:'微风'}
//解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render(){
console.log('render');
//读取状态
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//changeWeather调用几次? ———— 点几次调几次
changeWeather(){
//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
console.log('changeWeather');
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot:!isHot})
console.log(this);
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
5. 事件处理
1. 如何绑定事件
-
语法 on + 事件名称 = { 事件处理程序 } ,比如:
<div onClick={ onClick }></div>react会自动传递事件对象参数event
-
注意点
- react事件采用驼峰命名法,比如:onMouseEnter、onFocus
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
- 通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
-
样例
// 函数组件
function HelloFn () {
// 定义事件回调函数
const clickHandler = (event) => {
console.log('事件被触发了',event)
}
return (
// 绑定事件
<button onClick={clickHandler}>click me!</button>
)
}
2. 传递额外参数
改造事件绑定为箭头函数 在箭头函数中完成参数的传递
return (
<ul>
{list.map(item =>(
<li key={item.id}>
{item.name}
<button onClick={(e) => onDel(e, item.id)}>x</button>
</li>
))}
</ul>
)
3. 阻止默认事件
在React中,无法通过return false取消默认行为
- return false;
事件对象
-
- React事件中同样会传递事件对象,可以在响应函数中定义参数来接收
-
- React中的事件对象同样不是原生的事件对象,是经过React包装后的事件对象
-
- 由于对象进行过包装,所以使用过程中我们无需再去考虑兼容性问题
const clickHandler = (event) => {
event.preventDefault(); // 取消默认行为
event.stopPropagation(); // 取消事件的冒泡
alert('我是App中的clickHandler!');
};
6. state
state * state实际就是一个被React管理的变量 * 当我们通过setState()修改变量的值时,会触发组件的自动重新渲染
* 只有state值发生变化时,组件才会重新渲染
* 当state的值是一个对象时,修改时是使用新的对象去替换已有对象
* 当通过setState去修改一个state时,并不表示修改当前的state
* 它修改的是组件下一次渲染时state值
* setState()会触发组件的重新渲染,它是**异步的**
* 所以当调用setState()需要用旧state的值时,一定要注意
* 有可能出现计算错误的情况
* 为了避免这种情况,可以通过为setState()传递回调函数的形式来修改state值
```jsx
setCounter((prevCounter)=>{
/*
* setState()中回调函数的返回值将会成为新的state值
* 回调函数执行时,React会将最新的state值作为参数传递
* */
return prevCounter + 1;
});
```
7.Ref
何时使用 Refs
下面是几个适合使用 refs 的情况:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
使用Refs:
-
字符串refs (不推荐)
<input ref="input1" type="text" placeholder="点击按钮提示数据"/> //获取 this.refs.input1 -
回调函数形式refs
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/> //获取 showData2 = () => { const {input2} = this alert(input2.value) } -
createRef-API
-
createRef
const myRef = React.createRef()
<input ref={myRef} type="text" placeholder="点击按钮提示数据"/>
8. 组件之间通信
-
props ---- 父=》子
-
props可以传递任意数据
数字、字符串、布尔值、数组、对象、
函数、JSX
-
-
子=》 父
父组件给子组件传递回调函数,子组件调用
-
兄弟组件通信--状态提升机制(将数据给共同的父组件保管)
-
跨组件通信Context
-
实现步骤
1- 创建Context对象 导出 Provider 和 Consumer对象
const { Provider, Consumer } = createContext()2- 使用Provider包裹上层组件提供数据
<Provider value={xxx}> {/* 根组件 */} </Provider>3- 需要用到数据的组件使用Consumer包裹获取数据
<Consumer > {value => /* 基于 context 值进行渲染*/} </Consumer> -
完整代码
import React, { createContext } from 'react' // 1. 创建Context对象 const { Provider, Consumer } = createContext() // 3. 消费数据 function ComC() { return ( <Consumer > {value => <div>{value}</div>} </Consumer> ) } function ComA() { return ( <ComC/> ) } // 2. 提供数据 class App extends React.Component { state = { message: 'this is message' } render() { return ( <Provider value={this.state.message}> <div className="app"> <ComA /> </div> </Provider> ) } } export default App
-
props校验规则
目标任务: 掌握组件props的校验写法,增加组件的健壮性
对于组件来说,props是由外部传入的,我们其实无法保证组件使用者传入了什么格式的数据,如果传入的数据格式不对,就有可能会导致组件内部错误,有一个点很关键 - 组件的使用者可能报错了也不知道为什么,看下面的例子
实现步骤
- 安装属性校验包:
yarn add prop-types - 导入
prop-types包 - 使用
组件名.propTypes = {}给组件添加校验规则
import PropTypes from 'prop-types'
const List = props => {
const arr = props.colors
const lis = arr.map((item, index) => <li key={index}>{item.name}</li>)
return <ul>{lis}</ul>
}
List.propTypes = {
colors: PropTypes.array
}
props校验-规则说明
目标任务: 掌握props常见的规则
四种常见结构
- 常见类型:array、bool、func、number、object、string
- React元素类型:element
- 必填项:isRequired
- 特定的结构对象:shape({})
// 常见类型
optionalFunc: PropTypes.func,
// 必填 只需要在类型后面串联一个isRequired
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
Hooks
1. 什么是hooks
Hooks的本质:一套能够使函数组件更强大,更灵活的“钩子”
2. Hooks解决了什么问题
Hooks的出现解决了俩个问题 1. 组件的状态逻辑复用 2.class组件自身的问题
- 组件的逻辑复用 在hooks出现之前,react先后尝试了 mixins混入,HOC高阶组件,render-props等模式 但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等
- class组件自身的问题 class组件就像一个厚重的‘战舰’ 一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等,而我们更多时候需要的是一个轻快灵活的'快艇'
3. useState
作用
useState为函数组件提供状态(state)
使用步骤
- 导入
useState函数 - 调用
useState函数,并传入状态的初始值 - 从
useState函数的返回值中,拿到状态和修改状态的方法 - 在JSX中展示状态
- 调用修改状态的方法更新状态
import { useState } from 'react'
function App() {
// 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
// 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
const [count, setCount] = useState(0)
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
注意:useState 的初始值(参数)只会在组件第一次渲染时生效。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值
4. useEffect
1.什么是副作用
副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
常见的副作用
- 数据请求 ajax发送
- 手动修改dom
- localstorage操作
useEffect函数的作用就是为react函数组件提供副作用处理的!
-
报错为:Too many re-renders.
-
当我们直接在函数体中调用setState时,就会触发上述错误
-
——问题:
-
不是说过,当新的state值和旧值相同时,它是不会触发组件的重新渲染的
-
——setState()的执行流程(函数组件)
-
setCount() --> dispatchSetDate() --> 会先判断,组件当前处于什么阶段
-如果是渲染阶段 --> 不会检查state值是否相同 -如果不是渲染阶段 --> 会检查state的值是否相同 -如果值不相同,则对组件进行重新渲染 -如果值相同,则不对组件进行重新渲染 -如果值相同,React在一些情况下会继续执行当前组件的渲染 -但是这个渲染不会触发其子组件的渲染,这次渲染不会产生实际的效果 -这种情况通常发生在值第一次相同时 -
2. 基础使用
useEffect 是在组件渲染完毕后执行
- 导入
useEffect函数 - 调用
useEffect函数,并传入回调函数 - 在回调函数中编写副作用处理(dom操作)
- 修改数据状态
- 检测副作用是否生效
import { useEffect, useState } from 'react'
function App() {
const [count, setCount] = useState(0)
useEffect(()=>{
// dom操作
document.title = `当前已点击了${count}次`
})
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
3. 依赖项控制执行时机
在useEffect()可以传递一个第二个参数,
第二个参数是一个数组,在数组中可以指定Effect的依赖项
- 指定后,只有当依赖发生变化时,Effect才会被触发
- 通常会将Effect中使用的所有的局部变量都设置为依赖项
不添加依赖项
组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
- 组件初始渲染
- 组件更新 (不管是哪个状态引起的更新)
useEffect(()=>{
console.log('副作用执行了')
})
添加空数组
组件只在首次渲染时执行一次
useEffect(()=>{
console.log('副作用执行了')
},[])
添加特定依赖项
副作用函数在首次渲染时执行,在依赖项发生变化时重新执行
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('zs')
//只会在首次执行和count和name值改变时执行
useEffect(() => {
console.log('副作用执行了')
}, [count,name])
return (
<>
<button onClick={() => { setCount(count + 1) }}>{count}</button>
<button onClick={() => { setName('cp') }}>{name}</button>
</>
)
}
注意事项
useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug
4. 清理副作用
如果想要清理副作用 可以在副作用函数中的末尾return一个新的函数,在新的函数中编写清理副作用的逻辑
注意执行时机为:
- 组件卸载时自动执行
- 组件更新时,下一个useEffect副作用函数执行之前自动执行
import { useEffect, useState } from "react"
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
const timerId = setInterval(() => {
setCount(count + 1)
}, 1000)
return () => {
// 用来清理副作用的事情
clearInterval(timerId)
}
}, [count])
return (
<div>
{count}
</div>
)
}
export default App
5. useContext
子组件不用包裹,直接使用useContext接受。
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
6. useReducer
-
基础介绍
// useReducer(reducer, initialArg, init)
参数:
reducer : 整合函数
对于我们当前state的所有操作都应该在该函数中定义
该函数的返回值,会成为state的新值
reducer在执行时,会收到两个参数:
state 当前最新的state
action 它需要一个对象
在对象中会存储dispatch所发送的指令
initialArg : state的初始值,作用和useState()中的值是一样
返回值:
数组:
第一个参数,state 用来获取state的值
第二个参数,state 修改的派发器
通过派发器可以发送操作state的命令
具体的修改行为将会由另外一个函数(reducer)执行
-
使用
import React, {useReducer, useState} from 'react'; // 为了避免reducer会重复创建,通常reducer会定义到组件的外部 const countReducer = (state, action) => { switch (action.type) { case 'ADD': return state + 1; case 'SUB': return state - 1; default: return state; } }; const App = () => { // 模式使用方式 // const [count, setCount] = useState(1); // // const addHandler = () => { // setCount(prevState => prevState + 1); // }; // // const subHandler = () => { // setCount(prevState => prevState - 1); // }; const [count, countDispatch] = useReducer(countReducer, 1); const addHandler = () => { // 增加count的值 countDispatch({type: 'ADD'}); }; const subHandler = () => { // 增加count的值 countDispatch({type: 'SUB'}); }; return ( <div style={{fontSize: 30, width: 200, height: 200, margin: '100px auto', textAlign: 'center'}}> <button onClick={subHandler}>减少</button> {count} <button onClick={addHandler}>增加</button> </div> ); } ; export default App;
7. react-Memo
React.memo() 是一个高阶组件
它接收另一个组件作为参数,并且会返回一个包装过的新组件
包装过的新组件就会具有缓存功能,
包装过后,只有组件的props发生变化化
才会触发组件的重新的渲染,否则总是返回缓存中结果
B.js
import React from 'react';
const B = (props) => {
console.log('B渲染');
return (
<div>
<h2>组件B</h2>
<p>{props.test && '哈哈'}</p>
</div>
);
};
export default React.memo(B);
A.js
import React, {useState} from 'react';
import B from "./B";
const A = () => {
console.log('A渲染');
const [count, setCount] = useState(1);
const clickHandler = () => {
setCount(prevState => prevState + 1);
};
const test = count % 4 === 0;
return (
<div>
<h2>组件A -- {count}</h2>
<button onClick={clickHandler}>增加</button>
<B test={test}/>
</div>
);
};
export default React.memo(A);
App.js
import React, {useState} from 'react';
import A from "./components/A";
const App = () => {
console.log('App渲染');
const [count, setCount] = useState(1);
const clickHandler = () => {
setCount(prevState => prevState + 1);
};
return (
<div>
<h2>App -- {count}</h2>
<button onClick={clickHandler}>增加</button>
<A/>
</div>
);
};
export default App;
8. useCallback
useCallback 是一个钩子函数,用来创建React中的回调函数 useCallback 创建的回调函数不会总在组件重新渲染时重新创建
useCallback()
参数:
-
回调函数
-
依赖数组
- 当依赖数组中的变量发生变化时,回调函数才会重新创建
- 如果不指定依赖数组,回调函数每次都会重新创建
- 一定要将回调函数中使用到的所有变量都设置到依赖数组中
- 除了(setState)
import React, {useCallback, useState} from 'react';
import A from "./components/A";
const App = () => {
console.log('App渲染');
const [count, setCount] = useState(1);
const [num, setNum] = useState(1);
const clickHandler = useCallback(() => {
setCount(prevState => prevState + num);
setNum(prevState => num + 1);
}, [num]);
return (
<div>
<h2>App -- {count}</h2>
<button onClick={clickHandler}>增加</button>
<A onAdd={clickHandler}/>
</div>
);
};
export default App;
9. useRef
获取原生的DOM对象
- 可以使用传统的document来对DOM进行操作
2 直接从React处获取DOM对象 步骤: 1. 创建一个存储DOM对象的容器
````
1. 使用 **useRef()** 钩子函数
钩子函数的注意事项:
① React中的钩子函数只能用于函数组件或自定义钩子
② 钩子函数只能直接在函数组件中调用
2. 将容器设置为想要获取DOM对象元素的ref属性
```jsx
<h1 ref={xxx}>....</h1>
```
````
React会自动将当前元素的DOM对象,设置为容器current属性
useRef()
-
返回的就是一个普通的JS对象
{current:undefined} -
所以我们直接创建一个js对象,也可以代替useRef()
const h1Ref = {current:null}; -
区别
- 我们创建的对象,组件每次重新渲染都会创建一个新对象 useRef()创建的对象,可以确保每次渲染获取到的都是同一个对象
- 当你需要一个对象不会因为组件的重新渲染而改变时,就可以使用useRef()