一、前言
从这里开始进入React的分享,现在写的有些问题,后期等完整了解之后再重构一次内容!
二、初始化
安装:
npm i create-react-app -g
是否安装成功:
create-react-app -v // 如果存在版本号就表示安装成功
初始化项目:
// 使用create-react-app初始化项目:
create-react-app [项目名称] // js的项目
create-react-app [项目名称] --template typescript // ts的项目
// 使用npm初始化项目:
npm init react-app [项目名称] // js的项目
npm init react-app [项目名称] --template typescript // ts的项目
// 使用yarn初始化项目:(推荐,安装较快)
yarn create react-app [项目名称] // js的项目
yarn create react-app [项目名称] --template typescript // ts的项目
三、初始React
src文件夹下的index.js:
// react存在两个分支,一个是react-web(网页上渲染的东西),一个是react-native
// (原生开发),它们共用react.js,而在react-web中使用react-dom.js来渲染节点
import React from 'react'; // 负责核心逻辑(比如响应式)
import ReactDOM from 'react-dom/client'; // 负责渲染节点
import './index.css'; // 全局样式
import App from './App';
// createRoot创建根节点
const root = ReactDOM.createRoot(document.getElementById('root'));
// 在根节点渲染一个app组件
root.render(
<App />
); // 在渲染节点的过程中需要使用react.createElement()的方法来渲染组件
配置文件的覆盖:
// 安装craco(create-react-app-configuraton-override)
yarn add @craco/craco
// 创建配置文件craco.config.js(需要什么看文档去改写)
module.exports = {
}
// 改写package.json里面的脚本文件
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
// 集成Eslint(在vue中讲过每一步,这里不再说)
npx eslint --init // 这里会以选择的方式来配置eslint,选就行了
// 如果有需求配置eslint的冲突的话可以在.prettierrc文件中添加
当然,也可以集成Tailwindcss(链接),看文档很容易配置
注意:在react中,组件就是函数,但是函数的名字必须大写并且函数的返回值是可以渲染的(null、undefined、jsx元素、react组件、可迭代的数据)
// 环境变量的设置:
// 安装插件
yarn add cross-env
// 改写package.json里面的脚本文件
"scripts": {
"start": "cross-env NODE_ENV=development craco start",
"build": "cross-env NODE_ENV=production craco build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
// 当生产环境和开发环境的地址不一样的时候可以使用process.env.node_ENV来判断,
// 从而来切换地址
四、使用
(1)数据的渲染与视图的更新
// 举例:在App.jsx中
import React from 'react';
// 在打包运行之后页面上会渲染45这个字符串,也就是说怎么定义的就会怎么渲染
function App() {
let msg = '45'
return <div onClick={() => {
// 这里在点击这个标签之后msg表示的变量变化成了字符串'678',但是页面上
// 展示的数据还是'45',如果需要重新渲染的话,那就需要再将函数执行一次
// 因为函数的执行会导致页面的渲染
msg = '678'
}}>{ msg }</div>
}
export default App;
(2)常用的API
// useState:用来定义类似响应式数据的数据
// 举例:在App.jsx中
import React, { useState } from 'react';
function App() {
// 使用useState函数定义数据的话需要使用数组,这个数组有两个参数,第一个表示
// 定义的数据,第二个表示用来改变定义的值的函数,如果用这个函数来修改值的话
// 那么就会重新执行一次这个函数来刷新页面,从而起到响应式数据的作用。
let [msg, setMsg] = useState('45')
return <div onClick={() => {
msg = '678'
}}>{ msg }</div>
}
export default App;
// useCallback;缓存函数,避免函数的重复定义与销毁
// 举例:在App.jsx中
import React, { useState, useCallback } from 'react';
function App() {
const [count, setCount] = useState(1)
// 如果需要缓存的结果同步更新的话,useCallback的第二个参数是一个数组,
// 当这个数组里面的变量发生变化的时候,它会重新缓存最新的函数以及函数
// 的结果。
const fn = useCallback( () => {
setCount(count++)
console.log(count)
}, [count]) // 这里count发生变化的时候它会重新缓存count的最新值
// 当点击这个div的标签的时候,页面会渲染1,2,3..这表示点击的次数,但是
// 在控制台一直会打印2这个数字,因为这个函数被缓存了起来,当然,其结果
// 同样也会被缓存,这样在一直点击的时候,只会调用缓存中的函数,同样,也会
// 得到缓存中的结果。
return <div onClick={ fn }>{ count }</div>
}
export default App;
// useMemo;缓存计算结果,类似vue中的计算属性
// 举例:在App.jsx中
import React, { useState, useMemo } from 'react';
function App() {
const [A, setA] = useState(0)
const [B, setB] = useState(1)
// useMemo与useCallback有点类似,都是用来缓存,并且在后面数组中
// 的参数发生改变的时候会重新计算并缓存新的结果,只不过useCallback
// 返回的是一个函数(函数即组件),而useMemo返回的话是一个值,它们
// 数组中如果都不填的话那么函数返回的这个值是唯一不变的
const C = useMemo( () => a + b, [a, b])
return (
<>
{ C }
<button onClick={ () => {
setA(A++)
} }>改变A的值</button>
<button onClick={ () => {
setA(B++)
} }>改变B的值</button>
</>
)
}
export default App;
// useEffect:类似于vue中的mounted、beforeDestroy、watch
// 如果需要操作dom、bom、发送请求、打印(称为函数的副作用),这些操作
// 需要放在useEffect中执行(不存在上述操作的函数被称为纯函数)
// 举例:获取节点
import React, { useEffect } from 'react';
function App() {
// 对于useEffect来说,它也存在两个参数,第一个参数是一个函数,第二个
// 参数是一个函数需要依赖的参数,如果第二个参数什么都不填,那么useEffect
// 相当于vue中的mounted,如果填参数那么当这个参数发生变化的时候会重新执行
// 这个函数,当然,第一个函数参数可以返回一个函数,这个函数相当于vue中的
// boforeDestory,在这里用来清除定时器什么的。
useEffect( () => {
// react中组件的渲染是异步的,获取节点的操作是同步的,如果
// 不在这里获取的话可能会获取不到节点
const appElement = document.getElementByClassName('app')
console.log(appElement)
return () => {
// 相当于vue中的boforeDestory,在这里用来清除定时器什么的。
}
}, [appElement]) // 当appElement发生变化的时候会重新执行这个函数
return (
<>
<div className='app' />
</>
)
}
export default App;
// useRef:类似于useState,倒是它会更新数据,但是并不会重新渲染组件(页面)
// 不过它多了一个获取dom节点的操作
// 举例:获取节点(不使用dom操作)
import React, { useEffect, useRef } from 'react';
function App() {
const appElement = useRef(null) // 1.首先使用useRef定义一个变量
useEffect( () => {
console.log(appElement.current) // 在挂载完毕之后就可以获取到了
}, [])
return (
<>
<div ref={ appElement } /> // 2.然后使用把定义的变量使用ref属性
// 绑定需要获取的那个节点
</>
)
}
export default App;
组件视图更新的两种情况:
- 使用useState函数来修改数据
- 父组件的视图更新
注意: 组件某一时刻对应的数据对应的是该时刻的ui界面,这一刻ui界面渲染完毕之后,这个界面的数据是不可变的,如果需要更改数据那么更改后的数据就是对应的下一次的ui渲染的界面,也就是说,每次页面的渲染都会对应一组数据,这些数据是可以追溯且相互独立的
// useState:用来定义类似响应式数据的数据
// 举例:在App.jsx中
import React, { useState } from 'react';
function App() {
const [msg, setMsg] = useState(0)
return (
<>
{ msg }
<button onClick={ () => {
setMsg(msg++)
} }>增加msg的值</button> // 按钮1
<button onClick={ () => {
setTimeout( () => {
console.log(msg)
}, 5000)
} }>延时打印msg的值</button> // 按钮2
// 如果先点击按钮1再点击按钮2:页面的数据会从0变化到1,5秒后打印1
// 如果先点击按钮2再点击按钮1:页面的数据会从0变化到1,5秒后打印0
// 因为每次点击按钮后数据对应的ui界面不同。
// 情况1:
// 当我点击按钮1的时候msg的值由0变成1,再点击按钮2的时候只是打印
// 数据,并没有重新渲染数据,所以情况1两次点击按钮所对应的ui界面是
// 一致的,
// 情况2:
// 当我点击按钮2的时候它会先记录这个时刻页面中对应的msg的值也就是0,
// 之后我再去点击按钮1的时候它更改掉了msg的值让它的值由0变成了1,既
// 然数据更改了,当然ui也会重新渲染,此时ui对应的是msg的值为1这个界面
// 所以就会看到虽然页面从0到1但是5秒中打印的话打印的是0这个情况了。
</>
)
}
export default App;
(3)属性皆props
// APP.jsx
import React, { useState } from 'react';
import A from '@/A.jsx' // 引入一个A组件
function App() {
// 在A组件上添加了两个属性className和onClick,如果需要它们生效的话,需要用
// props在对应组件中声明才可以
return <A className='red' onClick={() => {
msg = '678'
}}>123</A>
}
export default App;
// A.jsx
import React, { useState } from 'react';
function A(props) {
// children的作用类似于插槽
const { className, onClick, children } = props
return (
// 空标签(<React.Fragment />)这个标签在渲染的时候不会被渲染出来
// 所以可以作为跟标签
<>
<div className={ className } onClick={ onClick }>123</div>
</>
)
}
export default A;
注意: 对于在W3C规定的标签上定义的属性react可以自己处理,会将对应的属性添加到指定的标签上面,而如果是自己定义的组件标签,需要自己人工处理使用props来转移到需要指定的组件标签上面
(4)双向数据绑定
import React, { useState } from 'react';
function App() {
const [value, setValue] = useState('')
return (
<>
// 双向数据绑定同样是一个input事件和一个value的属性
<input onInput={ (e) => {
// 在react中event对象被改写
setValue(e.target.value)
}} value={ value } />
</>
)
}
export default App;
五、路由
安装react-router-dom:
// 安装react-router-dom的时候它会自动安装react-router
yarn add react-router-dom -s
(1)基础配置
说明: 指明组件与路由的对应关系(什么路径渲染什么组件)
// App.jsx文件
import React from 'react'
// 路由模式
// BrowserRouter:历史记录模式
// HashRouter:hash值模式
import {
BrowserRouter,
HashRouter,
Routes,
Route,
Navigate, // 用于重定向
} from 'react-router-dom'
import Home from '@/home.' // 引入home组件
function App() {
return (
<>
// 最外层的标签表示用什么路由模式
<BrowserRouter>
// Routes标签里面的内容表示所有的路由记录
<Routes>
// 每一条路由记录用Route表示出来
// Navigate用作跳转组件,也就是当路径为'/'的时候,跳转到
// 指定的页面中去
<Route path='/' element={ <Navigate to='/home' /> } />
// 其中path属性表示路径,element表示需要渲染的组件
<Route path='/home' element={ <Home /> } />
// path如果填写*表示路径没有对应的页面的时候
<Route path='*' element={ <404 /> } />
// 路由的嵌套,在父页面使用outlet组件来确定子组件
// 渲染的位置(router-view的作用)
<Route path='/A' element={ <A /> }>
// 在嵌套路由里面,带有index属性的路由表示
// 父组件默认渲染的组件(不需要路径指定)
// 当然也可以使用路由重定向来指定
<Route path='' element={ <Navigate to='/B' /> } />
<Route index element={ <B /> }>
<Route path='/C' element={ <C /> }>
</Route>
</Routes>
</BrowserRouter>
</>
)
}
export default App;
(2)路由跳转
说明: 如果路径是以'/'开头表示绝对路由(直接替换),否则表示相对路由(相对于当前的URl发生改变)
假设当前存在以下路径:/home/order/all
做这样的处理: const navigate = useNavigate()
navigate('../abc') => /home/order/abc<=> 相对当前URL更改navigate('abc') => /home/order/all/abc<=> 相对当前URL更改navigate(-1) => /home/order<=> 相对当前URL更改navigate('/abc') => /abc<=> 直接替换
// NavLink组件和Link组件(声明式导航):
// NavLink组件和Link组件只能够在Router内部使用,它们的功能是一致的
// 区别在于NavLink组件可以用style或者是className修改样式,它们接受
// 一个函数,函数接受一个isActive字段的对象作为参数来调整样式,这两个
// 标签在任何地方都可以使用
// App.jsx文件
import React from 'react'
import {
BrowserRouter,
HashRouter,
Routes,
Route,
Navigate,
Link,
NavLink
} from 'react-router-dom'
import Home from '@/home.'
function App() {
return (
<>
<BrowserRouter>
<Link to='/home' />
// 可以设置样式
<NavLink to='/home' style={ ( {isActive} ) => ({
background: isActive ? 'red' : 'yellow'
}) } />
<Routes>
<Route path='/' element={ <Navigate to='/home' /> } />
<Route path='/home' element={ <Home /> } />
<Route path='*' element={ <404 /> } />
</Routes>
</BrowserRouter>
</>
)
}
export default App;
// useNavigate跳转(编程式导航):
// home.jsx文件
import React from 'react'
import {
useNavigate
} from 'react-router-dom' // 1.引入useNavigate
function Home() {
const navigate = useNavigate() // 2.执行一次useNavigate函数
return (
<>
// 3.useNavigate函数的参数就是需要跳转的路径
<button onClick={ () => navigate('/home') }/>
</>
)
}
export default Home;
(3)路由传参
// 路径传参(params参数):通过调用useParams函数取参数
// 路由配置页面:
<Routes>
// 传递参数
<Link to='/home/100/234' />
// 定义参数的位置
<Route path='/home/:id/:uid' element={ <Home /> } />
</Routes>
// 跳转的Home页面:
import React from 'react'
import { useParams } from 'react-router-dom' // 1.引入函数
function Home() {
const params = useParams() // 2.调用函数拿到参数
console.log(params)
return (
<>
<div></div>
</>
)
}
export default Home;
// search参数(query参数):通过调用useSearchParams函数取参数
// 路由配置页面:
<Routes>
// 直接在路径后面用?拼接参数,不需要先去定义参数的位置了
<Link to='/home?id=100' />
</Routes>
// 跳转的Home页面:
import React from 'react'
import { useSearchParams } from 'react-router-dom' // 1.引入函数
function Home() {
// useSearchParams函数执行的结果是一个数组,前面的表示取参数,
// 后面的表示设置参数
const [ SearchParams, setSearchParams] = useSearchParams()
// SearchParams是一个对象,取具体的值话需要通过get才能取到
console.log(SearchParams.get('参数名'))
// 对于setSearchParams设置参数,它会改变参数的值,只不过页面并不会跳转
return (
<>
<div></div>
</>
)
}
export default Home;
(4)懒加载
// React.lazy:可以将异步加载的组件promise包装成一个react的元素
// React.lazy不需要引入,而lazy需要引入,他们的作用是一样的
const Home = React.lazy( () => import('@/home') )
// 为了缓解用户的等待产生的焦虑,可以使用Suspense组件
import { Suspense } from 'react'
// 在页面在渲染的过程中,会渲染fallback里面的组件,直到加载成功
<Suspense fallback={ 组件 }>
<BrowserRouter>
<Routes>
<Route path='/' element={ <Navigate to='/home' /> } />
<Route path='/home' element={ <Home /> } />
<Route path='*' element={ <404 /> } />
</Routes>
</BrowserRouter>
</Suspense>