本文旨在记录react的基础内容,帮助有需要的同学快速上手,需要进一步了解描述更加稳妥和全面的信息,请查阅官方文档
官方文档点击这里进行跳转
React快速入门
先导
react框架
vue,react,angular这几种主流前端框架使用频率较高...本质还是js库。
React.js是一个用于构建用户界面的JavaScript库。它由Facebook开发并开源,被广泛应用于单页应用程序和移动应用开发中。
React采用组件化的开发方式,将用户界面拆分成独立的可重用组件。每个组件都有自己的状态(state)和属性(props),可以根据这些状态和属性来渲染出相应的UI。React使用虚拟DOM(Virtual DOM)来高效地更新和渲染界面,通过对比前后两个虚拟DOM的差异,只更新需要改变的部分,提高了性能。
React的核心思想是声明式编程,开发者只需关注界面应该是什么样子的,而不需要关心具体的更新过程。React还提供了丰富的生命周期方法和钩子函数,可以在组件不同的生命周期阶段执行相应的操作,方便开发者进行状态管理、数据处理和交互逻辑的处理。
React还具有良好的可扩展性和可组合性,可以与其他库和框架(如Redux、React Router等)无缝集成,方便构建复杂的应用程序。
react特点
- 声明式UI:抛弃命令式的写法,更简单一些
- 组件化:使用组件构建完整页面,降低了页面的耦合度
- 跨平台支持:无论是小程序,还是web端,都可以使用react进行开发
react初始搭建
-
使用脚手架创建(create react app),打开某个你能记得住具体位置的文件夹,然后在这个文件夹下面使用指令创建react项目
npx create-react-app react-basic(项目名称)
创建成功以后,生成结果如下
(注意一个问题,使用脚手架创建react项目的时候,不允许使用驼峰命名法,只允许使用小写字母+连字符链接)
创建成功后则会提示,并且产生一个命名好的文件夹,这就是所有的react文件的所在地点
接下来启动可以使用指令:
yarn start 或者 npm start
等待大约30秒以后,出现这个web界面即为成功,这个时刻react服务会占据你的3000端口(默认)
react的项目资源
初始创建出来的react文件应该是这样子的
其中,入口文件为index.js,内部结构如下,和vue中挂载的原理很接近
javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
//获取跟组件dom元素,并且根据这个元素创建节点
const root = ReactDOM.createRoot(document.getElementById('root'));
//然后把渲染挂载到这个dom节点上
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
- react为框架的核心包
- reactdom是专门做渲染的包
- App是根组件
- index.css是全局的框架
另外去掉严格模式节点: 18以后简易这样子处理会好一些
- 另外注意,linux下安装react是一样的逻辑和处理方法,难能可贵的一点
jsx
jsx是个啥
jsx就是JavaScriptHTML,在js中去书写html标签,文件名后缀可以是js文件,也可以是jsx文件
//举个例子
function App() {
return (
<div className="App"/>
);
}
优势:语法类似html,可以使用js的可编程性创建html,综合了以上两种的优点
注意:jsx是js语法的扩展,本身浏览器是不理解jsx语法的,在底层中react中有一个jsx的包帮助转化成js语法
jsx的具体语法
一些使用的案例
注意这些案例只是一些实际应用到jsx的情况,不是什么官方语法!可以简单学着写一写,理解为主
和模板语法很像 大致写法就是 {js的表达式} ,注意这里是表达式!
表达式:说白了表达式的意思就是数值,最简单的判断方法就是看能不能输出到控制台
列表渲染
比如我们想要实现一个数组的时候,可以通过map在遍历过程中创建标签,举个例子差不多是这样的
总结起来就是在js中写html自由度较高,但是想在html标签中写js表达式,要在中括号里写
并且这个规律是可以嵌套的
如何判断是不是表达式:能return,或者说能打印的
另外在jsx语法中,可以作为数值的东西,包括html标签捏
条件渲染
或者说选择性渲染,可以使用三元运算符和逻辑运算符,或者像这样手写if
例如这个列表中,我们对于id大于15的人进行显示
总之就是在html标签内部写js表达式的时候要加上{}
或者说满足另一个需求,符合条件才渲染(这个时候想要显示的html标签可以使用null来代替)
对于一些比较复杂,而且方向比较多的分支,建议将其抽象出来作为一个方法
在实际应用中,为了防止某些标签发生响应不及时,或者无法动态更新自己的属性的时候,我们还有一种肥肠丑陋但是有效的写法
{ isShow && <div>利用前面的布尔数值来判断这个是否显示</div>}
css样式处理
使用jsp语法可以对style实现样式的处理...不过这个玩意好像在js里也是有的
样式一共有两种控制方法:内联控制,以及第二种使用类名控制
很简单,并且第一种的第一个{}内部可以换成一个符合style规范的对象
另外类名可以动态控制,也可以有多个
注意事项
- jsx必须存在一个根节点,可以使用<></>(幽灵节点替代)
是不被允许的,必须有一个总领的标签元素
- jsx支持换行,如果换行最好使用()进行包裹,这样子代码可读性会更好一些
组件
组件是什么
组件化开发在vue中也是经常使用的,每个组件都是独立的,并且组件之间也可以进行通信,有利于复用和维护.
组件的两种类型以及渲染
- 函数组件
- 类组件
函数组件的创建和渲染
函数类组件的创建其实就是一个函数,返回值为一个div标签
function Hello(){
return <div>hello</div>
}
函数式组件的使用也是直接在父组件中使用
function App() {
return (
<div> <Hello/> </div>
);}
- 函数组件的要求是名称必须大写 ,不仅仅是语言规范,react内部也是通过标签的首字母判断这是html标签还是组件
类组件的创建和渲染
类组件的创建比较模板化,首先是继承react.component,然后是render必须有返回值
javascript
class HelloComponent extends React.Component{
render(){
return <div>hello this is a classComponent</div>
}
}
- 和函数组件一样,类组件名称首字母要大写
- 继承自React.Component类
- 要导入对应的包"React"
事件绑定
- 如何绑定事件
on+(对应的事件) = {触发的函数}
以下是一个比较标准的类组件中绑定事件的写法
javascript
class HelloComponent extends React.Component{
f1=()=>{
console.log("回调函数已经启动")
}
render(){
return <div onClick={this.f1}>hello this is a classComponent</div>
}
}
- 事件对象
想要获取事件对象本身,例如在上面这个地方获取到"点击事件"的具体信息
只需要在事件绑定的回调函数里面,加上参数e
f1=( e )=>{
console.log("事件的具体信息为",e)
}
render(){
return <div onClick={this.f1}>hello this is a classComponent</div>
}
按照上面的例子修改,则事件具体信息如下
得到事件以后,我们可以进行一些操作**:比如阻止默认跳转,阻止冒泡,懒加载等等**
- 事件绑定的函数传入额外参数
有些时候想要传入的是参数,或者说想要传入事件和参数两种argument,就用回调函数先把参数是一个事件这种事情,确定下来
f1=(e,num)=>{
console.log("事件为:",e,"传入的参数为",num)
}
render(){
return <div onClick={(e)=>this.f1(e,12)}>hello this is a classComponent</div>
}
/*解释一下为什么这样子写
首先是使用回调函数的形式,是为了第一步先行接收事件对象,后面即使在传入参数的时候,也不会把这个当成普通参数看待
如果是一开始就一同接收,只会当成一个普通的参数*/
组件状态(类组件为主)
- 状态的定义
状态的定义其实很简单,就是一个在类组件中,名为state的对象
在vue里面,我们可以对某个固定属性绑定v-model标签,这个标签可以让data和数据产生双向的响应的动态绑定.并且vue本身主打就是响应式.但是react有所不同,react的响应式效果没有vue那么强大,但是更加专一.
写在状态中的数据,是可以被响应式检测和监听的
在这个state中定义的所有属性,都是一个状态,其实这部分很类似vue中的data
- 组件的修改
组件内部数据的修改用到的方法比较粗暴,不允许我们直接去this.state.name=''xxxxx'这样修改
但是我们可以修改整个state(锁喉)
使用整个setState(对象)函数,在参数中填写一个新的state对象
(不然你以为它为什么叫setState...)
* 一些需要注意的问题
1.编写组件其实就是编写一个原生类/函数,定义状态必须使用state整个固定名称
2.修改state中的任何属性都不能通过直接赋值,只能通过setState方法,这个方法来自于继承
3.以及一些关于this的问题
//对象调用自己的方法,this就是当前的对象
//单独的全局函数调用,指向的是undefined(前提是严格模式,非严格模式(sloppy)下为自身)
//箭头函数没有自己的this,指向的是父上下文的this
//作为监听器的回调函数,指向的是监听的dom元素
4.一个老版本问题
以前的话,如果调用函数的时候不加上括号(),会让函数无法获取绑定到this属性
但是es6版本后更新了这个毛病,使用箭头函数可以自动绑定this
一些修改的小技巧:解构语法
用react做表单处理
目标:使用受控组件获取表单的数值
方法:受控组件,非受控组件
- 受控组件:举个例子,input框自己的属性被react的状态控制
手动实现类似v-model双向数据绑定的操作
<input className={'inputLog'} onInput={
()=>{//react中没有类似的语法糖,只能手动实现
const str=document.querySelector('.inputLog').value
this.setState({
name:this.state.name,
valueOfBlock:str
})
} }/>
这种是稍微原始一点的操作(一看就是刚学完es6)
首先获取到当前dom元素的value数值,然后根据这个value数值修改状态(这也是v-model的使用方法)
- 至于受控组件
则是数值不被state所控制,直接通过dom元素进行控制
组件之间通信
组件之间的通信可以分为以下情况和处理方法,由于函数和类组件的不同,可能还有不同的处理方式
- 父子通信
- 父传子
- 子传父
- 兄弟通信
- 其他通信
父子通信
父传子通信
父亲向子元素传递数据的方法和vue有些相似,但是原理8一样
- 父组件要传递的元素写在自己的state状态中
- 子组件接收数据,在自己的标签中用自定义名称接收
- 子组件使用数据的时候分两种情况
-
子组件为函数类组件的时候,需要在函数声明加上一个参数,这样会传入一个对象,对象内部就包含了父组件传递的数据(用的是自己定义的名称)
-
子组件为类组件的时候,可以在子组件内部直接使用this.props.xxx
class Father extends React.Component{
state={ name:'this is father`s data' } 父组件数据
render() {
return (父组件中调用子组件)
<SonOfClass msg={this.state.name}/>
<SonOfFunction msg={this.state.name}/>
}
}
class SonOfClass extends React.Component{ 类子组件
render() {
return ()
类组件接收数据的方法为{this.props.msg} 类子组件接收
}
}
function SonOfFunction(props) { 函数子组件
return (
函数组件接收数据的方法为{props.msg} 函数子组件接收(注意上面还有个函数)
)
}
-
子传父通信
子传父通信的原理为:子组件调用父组件传递过来的函数,并且把想要传递的数据当成函数的实参传入
父组件仍然是通过给子组件上标签的方法,给子组件传递一个函数对象
//父组件中的情况
met1=(num)=>{
console.log('子组件传递过来的消息为',num)//打印所有的方法
}
render() {
return (<div>
<SonOfClass method1={this.met1}/>//传入方法仍然是可以自定义名称的
</div>)
}
子组件可能会遇到一些问题,下面直接给出两种常用写法,再说为什么要这样
<div>
<button onClick={props.method1.bind(this,"数据")}>ddd</button>
<button onClick={ () => props.method1("数据") }>ddd2</button>
</div>
子组件调用的时候传入一些数据,然后父组件中就可以利用`这些数据修改点东西了
这里解释这两个button为什么要这样子写,因为我们要传入参数,对一个函数传入参数这种表达已经不符合"js表达式"
我们只有让返回值是一个传入参数的函数
- 第一种就是bind,众所周知,bind的作用在原生js中是绑定上下文对象,并且返回一个新的函数
- 第二种是使用箭头函数,返回值进行处理
兄弟通信
兄弟组件的通信,综合了以上两种方式,使用一个通过的父组件
A传递到Father组件,然后再由Father组件传递给B
这里不加解释了
其他通信
不是兄弟也不是父子的时候,通信方式就很抽象了
其实也不是很抽象,用到Provider和Consumer两个对象,这两个对象是在Context对象下的
-
第一步,先创建Context,然后从里面得到两个对象Provider和Consumer
(为了省点力气,这里我们直接使用解构语法)
(注意这里的defaultValue是默认数值,没啥影响)
通过解构语法直接得到传输对象
- 第二步,用****包裹父组件的根组件,然后在value属性中,写下自己想要传输的数据
- 第三步,在想要用到数据的地方,按照如下方法(固定格式),使进行传输数据
(注意Consumer不用包裹任何东西, 按照这个,里面写个jsx语法包裹回调函数就行力
这种传递数据的方法其实是父传子的延申,爷传孙罢
组件生命周期
首先注意,只有类组件由生命周期这个东西,虽然函数组件也存在更新和副作用的机制,但是本质上还是不一样的
组件的声明周期一共包括几个概念:
- 三个阶段:挂载/创建,更新,卸载
- 五个常用钩子函数
- 两个阶段:render commit
有一张图画的挺清晰明白的(模仿着画的)
每个阶段都是由上至下进行经历的,在此期间会触发钩子函数方便访问
挂载阶段:
constructor:创建组件的时候最先执行,初始化state,创建ref,使用bind绑定什么的
(但是前两种是过时写法了,现在不怎么用)
render:每次组件渲染(包括初次创建和每次更新)都会触发,负责渲染ui(这里不能用this)
componentDidMount:组件挂载并且完成渲染以后执行一次,这时候可以进行一些网络请求和dom操作
更新阶段
render:同上
componentDidUpdate:更新结束触发,可以直接获取dom元素
卸载阶段
componentWillUnMount:8用解释了吧
注意两点:
- render阶段是创建+渲染,在这个阶段是不允许我们调用this这个指向的
- 另外在render和componentDidUpdate中,都不能使用setState这种更新函数,因为更新会造成这些钩子的死循环调用
钩子函数的使用方法举例:
javascript
class Life extends React.Component{
constructor() {
super();
console.log("construct")
}
componentDidMount() {
console.log("挂载结束")
}
render() {
return (
<div>
</div>
)
}
}
关于hook
hook的本质也是一系列钩子函数,这些工资函数让函数组件也拥有了state
hook这个i能在函数组件中使用,解决了复用和class过于笨重的问题
- 常用函数:useState
该钩子函数的返回值为一个数组,数组的第一项和第二项,分别为数据和修改数据的方法
该函数输入参数为这个数据的初始数值
每次使用这个函数,都可以得到一个数据和对应的方法,允许多次调用获得多个state属性
const [count,setCount]=useState(0) //直接使用解构语法获得前面两项
//count:数据状态
//setCount(新数据):整体修改数据
这其中,setCount的本质和前面类组件的setState原理是一样的,都是用新数值替换旧数值,所以在需要一些复杂逻辑的时候,可以这样子写
//对于setCount(姑且这么称呼)这种函数,有一种情况为用回调函数作为参数
setCount( ()=>{
//编写运算逻辑.........
return //但是最后还是要返回东西
})
//这样子帮助我们解决了一些复杂问题,其实本质还没变....
关于具体的使用
javascript
function TestHook_useStated(){
//每一个state属性都要重新创建
const [count,setCount]=useState(0)
const [count2,setCount2]=useState(100)
const [count3,setCount3]=useState({age:12,name:'张三'})
//得到的修改数值的函数,参数就是新的state,本质和setState一样,用新数替换旧数值
return (
<>
<div onClick={()=>setCount(count+1)}>{count}</div>
<div onClick={()=>setCount2(count2-1)}>{count2}</div>
<div onClick={()=>setCount3({age:count3.age+1,name:'李四'})}>{count3.age},{count3.name}</div>
</>
)
}
- 常用函数useEffect
首先,有必要说明一下,什么是副作用
副作用:对于react来说,主作用就是根据内部数据渲染ui,除了主作用,剩下都是副作用,
比如dom操作,发送ajax请求,以及一些webapi的调用
useEffect就是干这个用的,把副作用放进这里面,维护函数的纯净
-
useEffect函数的执行时机是可以控制的,在默认条件下,执行是发生在任何一次更新(包括构建)
//但是可以通过依赖项目,控制时机 //useEffect有两个参数,一个参数为回调函数,另一个参数为监听数组 //1.默认情况下不加整个参数,则每次更新/重构都会执行 useEffect(()=>{}) //2.空数组,代表只执行一次 useEffect(()=>{},[]) //3,数组中的监听元素如果发生变化,则执行一次+初始化一次 useEffect(()=>{},[A,B,C])
-
useEffect对于一些副作用来说,是要进行消除的,函数内部提供了析构的方法,就是返回一个回调函数
useEffect(()=>{ return ()=>{ //关于清理的逻辑 } },[]) //这种就是只有初始化的时候执行一次的情况
-
关于具体的使用案例如下
function TestHook_useEffect(){ useEffect(()=>{ console.log("修改") document.querySelector('title').innerHTML=1; return ()=>{ //关于清理的逻辑 } },[]) //这种就是只有初始化的时候执行一次的情况 return( <div onClick={()=>console.log("修改")}> 点我测试 </div> ) }
路由
首先路由不是原生react中的内容,和vuex,redux一样,需要另外单独安装
(另外注意,v6版本是router的一个分水岭,我这里写的都是基于router6的内容)
下载指令为
yarn add react-router-dom@6
然后导入几个最重要的包
import {BrowserRouter, Link, Routes, Route} from 'react-router-dom'
就可以放心使用了
路由的核心组件
路由最重要的组件为四个
- Router(分为哈希式路由HashRouter和浏览器路由BrowersRouter)
- Link
- Routes
- Route
Router:最根本的展示区域,所有和路由相关的东西都应该包裹在内部
Link: 跳转路由的位置,需要在内部的to属性中写明将要跳转的地址,会在最后的h5界面中被变化为a标签
Routes:这个名字怪怪的,叫RouteContainer更好一些...
Route:路由组件,里面的path属性为开放端口,element属性则为具体的组件
路由会根据条件,从中跳转到合适的路由组件并且显示
举个例子:
<BrowserRouter>
<Link to='/page/10'>首页</Link>
<Link to='/about/?id=10'>关于</Link>
<Routes>
<Route path='/page/:id' element={<Page/>}/>
<Route path='/about' element={<About/>}/>
</Routes>
</BrowserRouter>
路由之间传递参数
路由传递参数有两种格式
-
第一种,使用Params传递
在父组件中,url中给传递数据的格式为如下格式
to中直接传递参数,path中使用占位符的方式获取参数
<Link to='/page/10'>首页</Link> <Route path='/page/:id' element={<Page/>}/>
子组件中,使用useParams获取一个对象
```
const params=useParams()
return(
<div>
输出结果为:{params.id}
</div>
)
```
-
第二种,使用SearchParams传递
在父组件中,url中给传递数据的格式为如下格式
to中通过url的get格式传递参数
<Link to='/page/?id=10'>首页</Link>
子组件中,使用useParams获取一个数组,数组的第一个是一个解构类似map的对象
const [params]=useSearchParams()
return(
<div>
输出结果为:{params.get("id")}
</div>
)
至于嵌套路由,这里直接举个例子
import { BrowserRouter as Router, Link, Route } from 'react-router-dom';
function ParentComponent() {
return (
<div>
<h1>父级组件内容</h1>
<li><Link to="/parent/child1">子路由1</Link></li>
<li><Link to="/parent/child2">子路由2</Link></li>
{/* 子级路由规则 */}
<Route path="/parent/child1" component={ChildComponent1} />
<Route path="/parent/child2" component={ChildComponent2} />
</div>
);
}
function ChildComponent1() {
return <h2>子组件1内容</h2>;
}
function ChildComponent2() {
return <h2>子组件2内容</h2>;
}
function App() {
return (
<Router>
{/* 路由规则和组件的对应关系 */}
<Route path="/parent" component={ParentComponent} />
</Router>
);
}
编程式导航
编程式导航,其实就是我们尝试使用代码逻辑去控制路由的跳转,而不是link
使用编程导航,主要一下两步骤
- 通过useNavigate获取一个跳转函数
- 调用跳转函数,输入路由和其他依赖作为参数,完成路由跳转
举个例子
const navigage=useNavigate()
const goPage=function (){
navigage('/page',{replace:true})
}
return(
<div>
<button onClick={goPage}>跳到首页</button>
</div>
)
//跳转函数里面自带两个参数
//里面两个参数,第一个参数要跳转到的路由地址
//第二个参数为一些配置信息的对象,比如replace:true 为不加入历史路径
//注意以上实现只能在<Router>组件中使用,换句话说就是在这个标签里面(就是哪两种核心组件)
//所以一般引入一个新的组件,比如routerLink(自己随便去起个名字吧)用来管理固有的信息和逻辑
//{最好在根组件外部创建}
#补充:关于状态管理工具
React笔记2
redux:集中状态管理,类似Java中的vuex
redux
redux的本体其实是一个状态管理工具,js的状态容器,在react中,通常用来存储一些通用的,需要响应化的属性
npm install --save redux
绑定react
npm install --save react-redux
创建开发者工具
store是根据reducer方法创建的
集中状态管理最终抽象一下可以理解为这样子
- 修改状态的方法:store发送action对象
- 获得状态的方法:store.getState()函数即可得到state对象
react-redux
redux本身支持很多东西,包括原生js,redux官方出品,专门用于react的绑定库,方便一些操作
- porvider:让store能全局化
- connect:让组件和store进行关联
基本使用
Provider:
包裹在根组件上(注意一定是根组件上),并且在这里往下传递一个store属性,让下面都能访问到store
const store=react.createStore()
root.render(
<Provider store={store}>
<App />
<TestRouter></TestRouter>
</Provider>
);
这样传递一个很显著的特点是,后面的组件都不需要再手动调用store来进行dispatch或者和getState
并且结合后面的两个方法,可以让发送action的方法,和state都整合进props里面
connect的参数:
mapStateToProps(state,ownProps),监听store是否被修改,并且把store中的state绑定到props中
mapDispatchToProps(dispatch,ownProps),把action绑定到props中
(上面两个函数的第二个参数可以不用写,因为把你需要的东西加入返回值里面,就会自动绑定到你的props中)
-
mapDispatchToProps:作用是将Action的构建方法以返回值的形式封装进props中,举个例子,主要用在发出修改的主页上面
//使用了这个绑定该方法以后,就会把你需要的东西绑定进props中 const misDispatchToProps=(dispatch)=>{ return{ sentAct:()=>{ 发送action对象的方法 dispatch({ 由于在根组件上已经提供了一个store,所以这里直接可以使用dispatch type:'send', value:a }) a++ } } } //首页组件 class ComA extends React.Component{ handleClick=()=>{ //发送action this.props.sentAct(); } render(){ return( <div> <button onClick={()=>{this.handleClick()}}> + </button> </div> ) } } //增强这个对象 export default connect(null,misDispatchToProps)(ComA)
-
mapStateToProps:**作用是将Store中更新的state接受下来,**并且以返回值的形式保存在props中,主要用在接收组件上面
//会把接收到的,被修改以后的state保存在props中 const mapStateToProps=(state)=>{ console.log('comb',state) return state } //首页组件 class Comb extends React.Component{ sta=this.props render() { return( <div> 显示数据{this.props.value} </div> ) } } //增强这个对象 export default connect(mapStateToProps,null)(Comb)
大致的逻辑结构可以为:
某种角度上来说,可以认为props托管了一部分本该有state发出的动作