这是一个 React 文档及相关资源的概览页面。
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
创建React项目:
1.通过webpack的方式,安装脚手架 react-create-app
安装方法(全局安装脚手架):
yarn global add create-react-app
npm install create-react-app -g
创建项目: create-react-app 项目名
来到项目根目录,运行项目:
npm run start
yarn start
2.vite方式:
1.创建:yarn create vite
success Installed "create-vite@3.1.0" with binaries:
- create-vite
- cva
2.? Project name: » vite-project 项目名字
3.选择 React
? Select a framework: » - Use arrow-keys. Return to submit.
Vanilla
Vue
React <--
Preact
Lit
Svelte
4.在选择 Js
? Select a variant: » - Use arrow-keys. Return to submit.
JavaScript <--
TypeScript
5.进入对应文件
6.安装依赖
7.运行 yarn dev
核心概念:jsx
jsx的出现将html与js结合在一起。jsx就是在js上做了扩展,是一种模板语法。
能够支持标签的应用及定义 如:
var a = <h1>1111</h1>
const element = <h1>Hello, world!</h1>;
也可以在jsx可以在js中任何地方直接定义节点
占位符
{},其中{}可以跟除对象以为的任何变量,如果是数组,数组里面装的一个一个的jsx,
react会帮我循环拿出来,渲染到页面
元素更新
react会更具改变的地方,进行最小力度更新,只更新改变了的地方
节点定义类
给一个节点定义类 className 等于原本的<h1 class="类名"></h1>
给一个节点定义 事假则用 onClick 必须用小驼峰命
react中的数据
react中数据的定义
类类型组件是一个class类。有一个属性专门用来存放组件状态,这个属性是state,state是一个对象
import React, {Component} from 'react'
export default class Index extends Component{
state = {
// 放入组件的状态
}
}
react中数据的改变
不能直接改变state中数据,得不到数据变了页面的效果
应该借助setState方法(类比原生微信小程序setData)
setState改变数据 如:
在类组件情况下
this.setState({
name: '李四'
})
在函数类型情况下 则没有this
通知视图更新
react中事件绑定
改变名字的小例子
import React, {Component} from 'react'
export default class Index extends Component{
state = {
// 放入组件的状态
name: '三字男明星'
}
// 定义改变名字的函数(普通函数、箭头函数)
chanName = () => {
// 改变名字 通知视图更新
this.setState({
name: '李四'
})
}
render() {
return (
<div>
{ this.state.name } <button>点击</button>
</div>
)
}
}
事件绑定中的this
- 只要记住:类类型组件里面,你定义的函数全是箭头函数,你就能逃过this的折磨。
- 有些开发者比较传统,还是喜欢普通函数。就是用bind来解决
事件传参
箭头函数 + bind
changeName = (name) => {
this.setState({
name
})
}
render() {
return (
<button onClick={this.changeName.bind(this, '王五')}></button>
)
}
react的组件
react组件的定义与使用
定义:创建一个jsx文件,通过rcc生成类类型组件 使用:引入,直接使用。
react组件的通信
父传子
通过props进行传递
// 父组件
render() {
return (
// 直接写在标签
<Child name="xxx"></Child>
// 类似于vue的插槽 子组件通过props.children
<Child>
内容
</Child>
)
}
// 子组件
class Child 继承 {
render() {
// dddd
console.log(this.props)
}
}
子传父
父组件的函数在子组件里面调用
父组件的函数传递子组件
子组件里面调用父组件的函数
通过函数传参的方式
```
// 父组件
fn = (data) => {
}
<Child fn={this.fn}></Child>
// 子组件
// 子组件直接执行
this.props.fn('数据')
```
条件渲染
React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) { return <UserGreeting />; } return <GuestGreeting />;}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />, document.getElementById('root'));
也可以使用三目运算符来进行条件判断。
react实现v-for
key是啥东西?唯一标识节点。作用:key是给react使用。更新的时候使用,达到最小粒度更新的效果。
注意:key不推荐使用索引,在某些情况下会特别消耗性能。 因为在使用索引的情况下倒序排列更新的话会对不上对应的索引,导致无法进行最小力度更新,索所以会浪费性能
循环:因为如果一个数组里装的是带标签的元素那么他会自动循环的页面上,所以通常使用map()来改变数据的结构来循环。
并且需要我们来给每个列表元素分配一个 key 属性 如下:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}> {number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
react中input的双向绑定
react中没有v-model指令,需要自己取实现
实现步骤:
定义一个状态用来表示input的值
将这个状态跟input的value绑定在一起
监听input框的输入事件
拿到输入的值
实时给react状态value赋值
import React, { Component } from 'react'
export default class Index extends Component {
constructor() {
super()
// vue的data
this.state = {
value: '是是是'
}
}
userInput = (e) => {
this.setState({
value: e.target.value
})
}
render() {
return (
<div>
{
// 不受控的input,input自己管理了一套状态。
// 监听用户的输入
// 拿到新值
// 将新值赋值给value
}
<input onInput={this.userInput} value={this.state.value} type="text" />
</div>
)
}
}
react的生命周期
挂载 (实例化组件)
constructor 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在
其他语句之前前调用 `super(props)`。否则,`this.props` 在构造函数中可能会出现未定义的bug。
render 渲染页面
componentDidMount 类比成vue的mouted 会在组件挂载后(插入 DOM 树中)立即调用。
依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求
的好地方。
获取dom节点
发起请求
更新(数据变化的时候就会更新,state,props)
shouldComponentUpdate
没啥作用,默认返回true。不写!!
render 渲染页面
componentDidUpdate 会在更新后会被立即调用。首次渲染不会执行此方法。
当组件更新后,可以在此处对 DOM 进行操作。
如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。
(例如,当 props 未发生变化时,则不会执行网络请求)。
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
销毁
componentWillUnmount 会在组件卸载及销毁之前直接调用。
在此方法中执行必要的清理操作,例如,清除 timer,
取消网络请求或清除在 `componentDidMount()` 中创建的订阅等。
render()
render()
render() 方法是 class 组件中唯一必须实现的方法。
当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:
- React 元素。通常通过 JSX 创建。例如,
<div />会被 React 渲染为 DOM 节点,<MyComponent />会被 React 渲染为自定义组件,无论是<div />还是<MyComponent />均为 React 元素。 - 数组或 fragments。 使得 render 方法可以返回多个元素。
- Portals。可以渲染子节点到不同的 DOM 子树中
- 字符串或数值类型。它们在 DOM 中会被渲染为文本节点
- 布尔类型或
null。什么都不渲染。(主要用于支持返回test && <Child />的模式,其中 test 为布尔类型。)
react中的路由
安装react的路由:react-router-dom (dom在jsx中配置)
yarn add react-router-dom
安装的是最新版本(v6),提供了两种配置路由的方式(传统、集中)
路由容器HashRouter,原则:所有跟路由相关的组件都必须放在HashRouter下面
推荐:把App使用HashRouter包起来(在main.jsx)
```
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import { HashRouter } from 'react-router-dom'
ReactDOM.createRoot(document.getElementById('root')).render(
<HashRouter>
<App />
</HashRouter>
)
```
配置一级路由
将path跟组件(页面)一一对应起来: Route
决定渲染哪一个Route的组件:Routes。Route必须被Routes包裹
重定向的组件:Navigate
```
// App.jsx
// 导入路由相关的所有组件
import { ... } from 'react-router-dom'
render() {
return (
<>
<Routes>
{ // 重定向,将 / 重定向到 /login }
<Route path="/" element={ <Navigate to="/login"></Navigate> }></Route>
<Route path="/home" element={<Home></Home>}></Route>
<Route path="/login" element={<Login></Login>}></Route>
{ // 404 }
<Route path="/*" element={<h1>404~~~~</h1>}></Route>
</Routes>
</>
)
}
```
函数组件
为什么会有函数组件?降低react的学习成本。
在vscode中快速创建的函数组件:rfc
import React from 'react'
export default function Index() {
// 页面结构
return (
<div>
</div>
)
}
优点: 语法简单, 没有this。
缺点:阉割版的react组件。没有生命周期与自身的状态(state)
API
cloneElement()
React.cloneElement(
element,
[props],
[...children]
)
以 element 元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,而来自原始元素的 key 和 ref 将被保留。
createFactory()
React.createFactory(type)
返回用于生成指定类型 React 元素的函数。与 React.createElement() 相似的是,类型参数既可以是标签名字符串(像是 'div' 或 'span'),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型。
isValidElement()
React.isValidElement(object)
验证对象是否为 React 元素,返回值为 true 或 false。
React.Children
React.Children 提供了用于处理 this.props.children 不透明数据结构的实用方法。
React.Children.map(children, function[(thisArg)])
在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。
React.Fragment
React.Fragment 组件能够在不额外创建 DOM 元素的情况下,让 render() 方法中返回多个元素。
render() {
return (
<React.Fragment>
Some text.
<h2>A heading</h2>
</React.Fragment>
);
}
你也可以使用其简写语法 <></>。
React.createRef
React.createRef 创建一个能够通过 ref 属性附加到 React 元素的 ref。
React.forwardRef
React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:
转发 refs 到 DOM 组件 在高阶组件中转发 refs
React.forwardRef 接受渲染函数作为参数。React 将使用 props 和 ref 作为参数来调用此函数。此函数应返回 React 节点。
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
React.lazy
React.lazy() 允许你定义一个动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。
// 这个组件是动态加载的
const SomeComponent = React.lazy(() => import('./SomeComponent'));
请注意,渲染 lazy 组件依赖该组件渲染树上层的 <React.Suspense> 组件。这是指定加载指示器(loading indicator)的方式。
Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性(生命周期)。
useState
明白:useState是一个方法
作用:让函数组件可以使用state
import React, {useState} from 'react'
export default function Index() {
// 定义一个自身的状态 能够改变这个状态(页面更新)
// useState返回一个数组,数组长度为2.第一个就是状态(需要你自己用一个变量来接它) 第二个参数:专门改变这个状态的方法
// const [ 状态,改变状态的方法 ] = useState(改状态的初始值)
const [num, setNum ] = useState(0)
// 定义一个方法
function add() {
// 累加
setNum(num + 1)
}
// 页面结构
return (
<div>
{num}
<button onClick={add}>add</button>
</div>
)
}
react改变对象/数组
讲究:不能改变原数组/对象
import React, {useState} from 'react'
export default function Index() {
// 定义一个自身的状态 能够改变这个状态(页面更新)
// useState返回一个数组,数组长度为2.第一个就是状态(需要你自己用一个变量来接它) 第二个参数:专门改变这个状态的方法
// const [ 状态,改变状态的方法 ] = useState(改状态的初始值)
const [num, setNum ] = useState(0)
const [arr, setArr] = useState(['a', 'b'])
// 定义一个方法
function add() {
// 累加
setArr([
...arr,
'c'
])
setObj({
...obj,
c: 12
})
}
// 页面结构
return (
<div>
<ul>
{
arr.map(v => <li key={v}>{v}</li>)
}
</ul>
<button onClick={add}>add</button>
</div>
)
}
Effect
Effect可以用来模拟class中的生命周期,可以不用在繁琐的记住对应的生命周期单词,只能在函数模式下使用
模拟componentDidMount
第二个参数传递空数组的时候
useEffect(() =>{
}, [])
模拟componentDidUpdate
- 第二个参数不传:组件任何状态改变都会进来
- 第二个参数是一个数组,数组里面是监听的状态,相应的状态改变才会进来 (类比watch来学习)
const [num, setNum] = useState(0)
const [name, setName] = useState('张三')
// componentDidMount
useEffect(() => {
// 发起请求 获取节点
console.log(document.getElementById('index'))
}, [])
// componentDidUpdate(第一次加载完会执行)
useEffect(() => {
console.log('第二个参数不传,组件的任何状态改变都会进来')
})
useEffect(()=>{
console.log('num改变之后才会进来')
}, [num])
useEffect(()=>{
console.log('name改变之后才会进来')
}, [name])
useEffect(()=>{
console.log('name或者num改变之后才会进来')
}, [name, num])
模拟componentWillUnmount
useEffect(() => {
// 发起请求 获取节点
console.log(document.getElementById('index'))
let timer = setInterval(() => {
console.log('xxxxxxx')
}, 500)
// componentWillUnmount
return () => {
// 代表组件卸载之后的回调
console.log('组件卸载了!!!')
clearInterval(timer)
}
}, [])
只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
useContext
const value = useContext(MyContext);
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。
别忘记 useContext 的参数必须是 context 对象本身:
- 正确:
useContext(MyContext) - 错误:
useContext(MyContext.Consumer) - 错误:
useContext(MyContext.Provider)
调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化
useReducer
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
const [state, dispatch] = useReducer(reducer, initialArg, init);
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
返回一个 memoized 回调函数。
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized值。
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。
如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。 将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。
useRef
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
一个常见的用例便是命令式地访问子组件:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
尽可能使用标准的 useEffect 以避免阻塞视觉更新。
useDebugValue
useDebugValue(value)
useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。
例如,“自定义 Hook” 章节中描述的名为 useFriendStatus 的自定义 Hook:
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签 // e.g. "FriendStatus: Online" useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}