React基础知识

React——React基础知识记录,持续更新...

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>
  )
}

组件实例的三大核心属性:statepropsrefs与事件处理

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函数,调用的返回值依然是一个函数。

(常见的高阶函数:PromisesetTimeout数组.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: ''})
<!--1useState调用后返回一个数组-->
<!--2数组中第一个元素是状态变量第二个元素是set函数用来修改状态变量的值-->
<!--3useState的参数将作为状态变量的初始值-->

注意:直接修改状态变量时(例:count = 1person.sex = '男'),状态变量的值会变化,但是不能引发视图更新。

函数组件setState()的执行流程: 调用后首先会去找React DOM中的一个函数dispatchSetDate(),这个函数会先判断组件当前处于什么阶段:

  • 如果是渲染阶段,不会检查state值和旧值是否相等,都会触发组件的重新渲染;
  • 如果不是渲染阶段,会检查state值和旧值是否相等,如果不相等,则会触发组件的重新渲染;如果相等,则不会触发组件的重新渲染(React在一些情况下(通常发生在state值和旧值值第一次相等时)会继续执行当前组件的渲染,但这个渲染并不会产生实际效果,且不会触发其子组件的重新渲染)

受控表单绑定

state绑定到inputvalue属性,同步把input最新的value值设置给state

  1. 初始化一个状态值
1
const [inputValue, setInputValue] = useState('')
  1. 通过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
  1. 使用useRef创建一个初始值为nullref对象,并将ref对象作为ref属性传递给想要操作的DOM节点的JSX
  2. 当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} />
    )
}

组件通信

  • 父子通信
  1. 父组件传递数据:在子组件标签上绑定属性;
  2. 子组件接收数据:通过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>
  );
}
  • 使用 Context 深层传递参数
  1. 在任意组件外使用 createContext 创建一个上下文对象SomeContext
1
const ThemeContext = createContext('light');
  1. 用上下文 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>
  );
}
  1. Provider 包裹的组件可通过调用 useContext(SomeContext) 获取上方距离它最近的上下文 providervalue
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 返回一个由两个值组成的数组:

  1. state:用于获取state的值。
  2. 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依赖不变的情况下,不会在组件重新渲染时重新创建,而是返回相同的函数。 参数

  • fn:想要缓存的函数,此函数可以接受任何参数并且返回任何值。
  • dependencies:是否更新 fn 的依赖(写法:[dep1, dep2, dep3],React使用Object.is比较每一个依赖和它的之前的值)。三种情况区别:传递依赖项数组(初始渲染后以及依赖项变更的重新渲染后运行)、传递空依赖项数组(仅在初始渲染后运行)、不传递依赖项数组(在组件的每次单独渲染(和重新渲染)之后运行)。 返回值 在初次渲染时,useCallback 返回你已经传入的 fn 函数。 在之后的渲染中, 如果依赖没有改变,useCallback 返回上一次渲染中缓存的 fn 函数;否则返回这一次渲染传入的 fn。 ```jsx import { useCallback, useState } from “react”; import Son from “../Son”; const Father = () => { console.log(‘Father渲染了’) const [count, setCount] = useState(0) const handleAdd = useCallback(() => { setCount((val) => val + 1) }, []) return ( <> <button onClick={handleAdd}>Father ADD</button> <h3>Father count:{count}</h3> <Son onAdd={handleAdd}></Son> </> ); } export default Father; import { memo, useState } from “react”; const Son = (props) => { console.log(‘Son渲染了’) const [count, setCount] = useState(0) return (
    <button onClick={() => setCount((val) => val + 1)}>Son ADD</button> <button onClick={props.onAdd}>Son修改Father</button>

    Son count:{count}

    ); } export default memo(Son);

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>
);
  • 路由导航 多个路由之间进行路由跳转,并且在跳转的同时可以传递参数进行通信。
  1. 声明式导航:在模板中通过<Link/>组件的to属性指定要跳转的路由path。组件最终会被渲染为浏览器支持的a链接,如需传参可以直接通过字符串拼接的方式拼接参数。
1
<Link to="/login">跳转</Link>
  1. 编程式导航:通过调用useNavigate钩子返回一个函数,再调用返回的函数传入地址path实现跳转。
1
2
3
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
<button onClick={() => {navigate('/login')}}>跳转</button>
  • 路由传参
  1. 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: '无题'}
  1. 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模块,避免类名冲突)

  1. 创建一个xxx.module.css文件
  2. 在组件中引入该css文件:import classes from './xxx.module.css'
  3. 通过classname来设置类名:<div classname={classes.box}></div>

注:

  1. CSS模块可以动态的生成唯一的class值,相当于Vue的scoped局限。
  2. 使用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>
    )
}


-->