JSX知识
在JSX
中可以通过大括号{}
识别JS中的表达式,比如常见的变量、函数调用、方法调用等等。
1
2
3
4
5
6
7
8
| function Hello () {
const list = [{label: 'aaa', value: 'aaa'},{label: 'bbb', value: 'bbb'}]
return (
<ul>
{list.map(item => <li key={item.value}>{item.label}</li>)}
</ul>
)
}
|
1
2
3
4
5
6
7
8
9
| function Hello () {
const flag = true
return (
<div>
{flag && 'true'}
{flag ? 'true' : 'false'}
</div>
)
}
|
组件实例的三大核心属性:state
、props
、refs与事件处理
state
state
是组件对象最重要的属性,值是对象(包含多个key-value的组合)。
注意:状态(state)不可直接更改,需要借助react内置的API:setState
setState
使用setState
更新状态时,传入的对象是合并原有的state
,不是替换。
1
2
3
| this.state = { name: 'Amy', age: 18 }
this.setState({name: 'Jhon'})
console.log(this.state) // {name: 'Jhon', age: 18}
|
props
每个组件对象都会有props
属性,用于保存组件标签的所有属性。
作用:通过标签属性从组件外向组件内传递变化的数据(组件内不能修改props数据)
对props
中的属性值进行类型限制和必要性限制:在Reactv15.5后需要引入prop-types
库
1
2
3
4
5
6
7
8
9
10
11
| <!--类型限制和必要性限制-->
Person.propTypes = {
name: PropTypes.string.isRequired, //字符串类型并且必传
age: PropTypes.number,
say: PropTypes.func
}
<!--默认属性值-->
Person.defaultProps = {
age: 18,
name: 'Amy'
}
|
ref
1、字符串形式ref:直接在元素上添加ref
属性,通过refs.ref属性值
获取元素对象;
1
| <input ref="myInput" type="text">
|
1
2
| // 获取元素对象
const dom = refs.myInput
|
2、回调形式ref:在元素上添加ref
属性,属性值是一个回调函数,通过参数获取元素对象;
1
| <input ref={func} type="text">
|
1
2
3
4
| // 获取元素对象
const func = (element) => {
const dom = element
}
|
3、createRef创建ref容器:React.createRef
调用后可以返回一个容器,该容器可以存储被ref
所标记的元素;
1
| <input ref={myRef} type="text">
|
1
2
| // 创建ref对象存储的容器
const myRef = React.createRef();
|
事件处理
1、通过onXxx
属性指定事件处理函数
React使用的是自定义事件,而不是使用原生DOM事件 – 为了更好的兼容性
React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) – 为了高效
2、通过event.target
得到发生事件的DOM元素对象 – 不要过渡使用ref
3、传递事件参数和自定义参数
1
2
3
4
5
6
| function Hello () {
const handleClick = (e, name) => {
console.log(e, name);
}
return (<button onClick={(e) => { handleClick(e, 'Jhon') }}>按钮</button>)
}
|
高阶函数与函数柯里化
高阶函数:如果一个函数符合以下2个规范中的任何一个,那么该函数就是高阶函数。
1、若A函数,接收的参数是一个函数;
2、若A函数,调用的返回值依然是一个函数。
(常见的高阶函数:Promise
、setTimeout
、数组.map
等等)
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
1
2
3
4
5
6
7
| function sum (a) {
return (b) => {
return (c) => {
return a + b + c
}
}
}
|
useState
useState
是一个React Hook
函数,它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果。
1
2
3
4
5
6
7
| const [count, setCount] = useState(0);
setCount(1)
const [person, setPerson] = useState({name: 'Jhon', age: 20});
setPerson({...person, sex: '男'})
<!--1、useState调用后返回一个数组;-->
<!--2、数组中第一个元素是状态变量,第二个元素是set函数用来修改状态变量的值;-->
<!--3、useState的参数将作为状态变量的初始值。-->
|
注意:直接修改状态变量时(例:count = 1
或person.sex = '男'
),状态变量的值会变化,但是不能引发视图更新。
函数组件setState()
的执行流程:
调用后首先会去找React DOM中的一个函数dispatchSetDate()
,这个函数会先判断组件当前处于什么阶段:
- 如果是渲染阶段,不会检查state值和旧值是否相等,都会触发组件的重新渲染;
- 如果不是渲染阶段,会检查state值和旧值是否相等,如果不相等,则会触发组件的重新渲染;如果相等,则不会触发组件的重新渲染(React在一些情况下(通常发生在state值和旧值值第一次相等时)会继续执行当前组件的渲染,但这个渲染并不会产生实际效果,且不会触发其子组件的重新渲染)
受控表单绑定
state
绑定到input
的value
属性,同步把input
最新的value
值设置给state
- 初始化一个状态值
1
| const [inputValue, setInputValue] = useState('')
|
- 通过
input
标签的value
属性绑定状态;绑定onChange
事件,通过事件参数拿到最新的value值反向修改inputValue
状态值
1
| <input value={inputValue} onChange={(e) => setInputValue(e.target.value)}/>
|
useRef(initialValue:初始值)
useRef
返回一个只有一个current
属性的对象,可以修改ref.current
属性但是不会重新渲染组件,这意味着 ref 是存储一些不影响组件视图输出信息的完美选择
注意:除了初始化外不要在渲染期间写入或者读取ref.current
,可以在事件处理程序或者Effect中读取和写入ref。
1
2
3
4
5
6
| useEffect(() => {
myRef.current = 123;
});
function handleClick() {
doSomething(myOtherRef.current);
}
|
通过 ref 操作 DOM
- 使用
useRef
创建一个初始值为null
的ref
对象,并将ref对象作为ref属性传递给想要操作的DOM节点的JSX
- 当DOM渲染完成后,通过创建的
ref
对象上的current
属性拿到DOM对象
1
2
3
4
5
6
7
8
9
| const Foo = () => {
const inputRef = useRef(null)
setTimeout(() => {
console.log(inputRef.current)
}, 3000)
return (
<input type='text' ref={inputRef} />
)
}
|
组件通信
- 父组件传递数据:在子组件标签上绑定属性;
- 子组件接收数据:通过
props参数
接收数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| const Son = (props) => {
console.log(props); // props对象包含了父组件传递过来的所有数据
return (
<p>我是子组件,从父组件拿到{props.name}</p>
)
}
const Father = () => {
return (
<div>
<p>我是父组件</p>
<Son name={'Jhon'} obj=></Son>
</div>
);
}
|
注意:
props
可传递人一类型的数据(数字、字符串、布尔值、数组、对象、函数、JSX);
子组件只能读取props
中的数据,不能直接修改,只能通过父组件自己修改。
特殊的props.children
:当我们把内容嵌套在子组件标签中时,在子组件的props参数
上的children
属性会接收该内容
1
2
3
| <Son name={'Jhon'}>
<span>嵌套的内容</span>
</Son>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| const Son = ({msg, onSetMsg}) => {
return (
<>
<p>我是子组件,从父组件拿到{msg}</p>
<button onClick={() => onSetMsg('111')}>修改</button>
</>
)
}
const Father = () => {
const [msg, setMsg] = useState('000')
return (
<div>
<p>我是父组件,{msg}</p>
<Son msg={msg} onSetMsg={setMsg}></Son>
</div>
);
}
|
- 在任意组件外使用
createContext
创建一个上下文对象SomeContext
;
1
| const ThemeContext = createContext('light');
|
- 用上下文
Provider
包裹组件,为里面所有的组件指定一个上下文的值(value
:可以为任何类型)
1
2
3
4
5
6
7
8
| function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<Page />
</ThemeContext.Provider>
);
}
|
- 被
Provider
包裹的组件可通过调用 useContext(SomeContext)
获取上方距离它最近的上下文 provider
的 value
1
2
3
4
| function Button() {
const theme = useContext(ThemeContext);
return <button className={theme} />;
}
|
useEffect(setup, dependencies?)
用于创建不是由事件引起而是由渲染本身引起的操作。
参数
- 参数1:一个函数(副作用函数),在函数内部可以放置要执行的操作,可选择性返回一个 清理(cleanup) 函数。当组件被添加到 DOM 的时候,React 将运行
setup
函数。在每次依赖项变更重新渲染后,React
将首先使用旧值运行 cleanup
函数(如果你提供了该函数),然后使用新值运行 setup
函数。在组件从 DOM
中移除后,React
将最后一次运行 cleanup
函数。
- 参数2:一个数组(可选)。三种情况区别:传递依赖项数组(初始渲染后以及依赖项变更的重新渲染后运行)、传递空依赖项数组(仅在初始渲染后运行)、不传递依赖项数组(在组件的每次单独渲染(和重新渲染)之后运行)。
1
2
3
4
5
6
7
8
9
10
11
| import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => { // 返回cleanup函数
connection.disconnect();
};
}, [serverUrl, roomId]);
}
|
1
2
3
4
5
6
7
8
9
10
| <!--利用清理函数做输入框搜索的节流操作-->
const [keyWord, setKeyword] = useState('')
useEffect(() => {
const timer = setTimeout(() => {
//...调用搜索接口
}, 1000)
return () => {
clearTimeout(timer)
}
}, [keyWord])
|
注意:通常将Effect
中使用的所有局部变量都设置为依赖项(useState()
会确保组件的每次渲染都会获取到相同setState
对象,所以setState()方法
可以不设置到依赖中)
useReducer(reducer, initialArg, init?)
参数
reducer
:整合函数,参数为state
(当前的state)和action
(通常是一个对象,通过dispatch函数的参数传递)。对于当前state
的所有操作都在该函数中定义,该函数返回值是state
的新值。
initialArg
:初始化 state 的任意值。
可选参数 init
:用于计算初始值的函数。如果存在,使用 init(initialArg)
的执行结果作为初始值,否则使用 initialArg
。
返回值
useReducer
返回一个由两个值组成的数组:
state
:用于获取state
的值。
dispatch 函数
:state 修改的派发器,具体修改行为将由reducer
整合函数执行,用于更新 state 并触发组件的重新渲染,需要传入一个 action
作为参数。该函数没有返回值。
注意:为了避免 reducer 会重复渲染,通常 reducer 会定义在组件的外部
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| <!--在组件外部定义一个reducer-->
const countReducer = (state, action) => {
switch(action.type){
case 'ADD':
return state + 1;
case 'SUB':
return state - 1;
default:
return state;
}
}
<!--函数组件-->
const APP = () => {
const [state, countDispatch] = useReducer(countReducer, 1);
const addHandler = () => {
countDispatch({type: 'ADD'})
}
return <>
<button>ADD</button>
</>
}
|
memo
接收一个组件作为参数,并返回一个具有缓存功能的新组件,只要该组件的props
没有改变,就不会在其父组件重新渲染时重新渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| <!--父组件-->
import { useState } from "react";
import Son from "../Son";
const Father = () => {
console.log('Father渲染了')
const [count, setCount] = useState(0)
const flagProp = count % 4 === 0
return (
<>
<button onClick={() => setCount((val) => val + 1)}>Father ADD</button>
<h3>Father count:{count}</h3>
<Son flagProp={flagProp}></Son>
</>
);
}
export default Father;
<!--子组件-->
import { memo, useState } from "react";
const Son = (props) => {
console.log('Son渲染了')
const [count, setCount] = useState(0)
return (
<div>
<button onClick={() => setCount((val) => val + 1)}>Son ADD</button>
<h3>Son count:{count} - {props.flagProp.toString()}</h3>
</div>
);
}
export default memo(Son);
|
useCallback(fn, 返回相同的函数)
在组件初次渲染而非fn
调用时返回一个回调函数,该回调函数在dependencies
依赖不变的情况下,不会在组件重新渲染时重新创建,而是返回相同的函数。
参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
### 路由`react-router-dom`
`npm install react-router-dom`
```jsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Login from "../pages/Login";
const router = createBrowserRouter([
{
path: '/login',
element: <Login/>
}
])
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
|
- 路由导航
多个路由之间进行路由跳转,并且在跳转的同时可以传递参数进行通信。
- 声明式导航:在模板中通过
<Link/>
组件的to
属性指定要跳转的路由path
。组件最终会被渲染为浏览器支持的a链接
,如需传参可以直接通过字符串拼接的方式拼接参数。
1
| <Link to="/login">跳转</Link>
|
- 编程式导航:通过调用
useNavigate
钩子返回一个函数,再调用返回的函数传入地址path
实现跳转。
1
2
3
| import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
<button onClick={() => {navigate('/login')}}>跳转</button>
|
- params传参:在配置路由规则的
path
时定义参数,跳转时传入相应地址和参数,通过useParams
钩子获取参数
1
2
3
4
5
6
7
8
9
10
| <!--路由规则-->
{
path: '/article/:id/:title',
element: <Article/>
}
<!--跳转传参-->
<button onClick={() => {navigate('/article/111/无题')}}>跳转</button>
<!--获取参数-->
import { useParams } from "react-router-dom"
const params = useParams() //params:{id: '111', title: '无题'}
|
- search传参:直接通过
?
的形式携带参数,通过useSearchParams
来访问或修改查询参数
1
2
3
4
5
6
7
| <!--跳转传参-->
<button onClick={() => {navigate('/article?id=111&name=无题')}}>search跳转</button>
<!--获取参数-->
import { useSearchParams } from "react-router-dom"
const [searchParams, setSearchParams] = useSearchParams();
searchParams.get('id') // 111
setSearchParams({ id: 222, name: 'aaa'}) // 修改(必须传入所有的查询参数,否则会覆盖已有参数)
|
- 路由嵌套
先使用
children
属性配置路由嵌套关系,然后在上层路由使用<Outlet/>
组件配置下层路由的渲染位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| <!--配置路由关系-->
{
path: '/',
element: <Layout/>,
children: [
{
index: true, //默认二级路由
element: <Home/>
},
{
path: '/about',
element: <About/>
},
]
}
<!--设置渲染位置-->
import { Outlet } from "react-router-dom";
const Layout = () => {
return (
<div>
<p>这是Article</p>
<Outlet />
</div>
)
}
|
- 404路由
准备一个路由404时跳转的组件,在路由表数组的末尾,以
*
号作为路由path
配置路由
1
2
3
4
| {
path: '*',
element: <NotFound />
}
|
- 路由模式
history模式
:由createBrowserRouter
函数创建,底层原理由history对象
+pushState事件
。
hash模式
:由createHashRouter
函数创建,底层原理由监hashChange事件
。
CSS样式
普通的 CSS 文件(全局样式)
项目中所有普通css文件里面的类名如果相同,会相互污染。
1
2
3
4
5
6
7
8
9
10
11
12
13
| // A组件内的index.css文件
.aaa{
color: '#fff';
}
// B组件内的index.css文件
.aaa{
color: '#000';
}
// A组件内的index.jsx文件
import '.A/styles.css'
function func(){
return <div classname="aaa"></div> // 此处div元素的样式同时被A/B组件内的index.css文件影响
}
|
CSS Module(CSS模块,避免类名冲突)
- 创建一个
xxx.module.css
文件
- 在组件中引入该css文件:
import classes from './xxx.module.css'
- 通过
classname
来设置类名:<div classname={classes.box}></div>
注:
- CSS模块可以动态的生成唯一的class值,相当于Vue的
scoped
局限。
- 使用Umi搭建的项目,可以直接引入
xxx.css
样式文件并存到一个变量上并赋值到classname
属性,就自动将样式以 CSS Module
的形式引入,不用引入像这种xxx.module.css
文件也可实现样式不被污染。要注意,如果classname
属性赋值为字符串,则无效,样式会被其他组件的同名类名相互污染。
```js
// 在umi项目中,CSS Module
自动生效
import styles from “./index.css”;
function func(){
return <div classname={styles}></div>
}
// 在umi项目中,CSS Module
不会生效,同类名样式会相互污染
// index.css文件
.aaa{
background-color: “#f00”;
}
// index.jsx文件
import ‘./index.css’
function func(){
return <div classname="aaa"></div>
}
1
2
3
4
5
6
7
8
9
10
| **内联样式**
通过 `style` 属性来设置样式,只对当前元素及其子元素有效,建议只用于简单的样式场景
```js
const style = {
fontSize: '16px',
color: '#f00'
}
function func(){
return <div style={style}></div>
}
|
React.Fragment
这是一个专门用来作为父容器的组件,它只会将它里面的子元素直接返回,不会创建任何多余的元素
1
2
3
4
5
6
7
8
| import React from 'react'
const App = () => {
return (
<React.Fragment>
<div>...</div>
</React.Fragment>
)
}
|