React
一、React基础
1.React概述
1.1 什么是React
-
React是一个用于构建用户界面的JavaScript库
- 用户界面:HTML页面(前端)
-
React主要是用来写HTML页面,或者构建Web应用
-
如果从MVC的角度来说,React仅仅是视图层,也就是只负责视图的渲染,而并非提供了完整的M,C的功能
1.2React的特点
- 声明式
- 基于组件
2.React基础使用
2.1React的安装
npm i react react-dom
- react 包是核心,提供创建元素、组件等功能
- react-dom 包提供DOM相关功能等
2.2React的使用
-
引入react和react-dom
<script src="./node_modules//react/umd/react.development.js"></script> <script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
-
创建React元素
const title=React.createElement('h1',null,'HelloWorld!') //参数:元素名称,元素属性,元素子节点1,元素子节点2... React.createElement(img,{src:'xxx',style:'width:200px;'},'HelloWorld!')
-
渲染React元素到页面中
ReactDOM.render(title,document.getElementById('root')) //参数:要渲染的元素,元素挂载点
3.React脚手架的使用
3.1使用React脚手架初始化项目
- 初始化项目
npx create-react-app 项目名称
- npx 是 npm v5.2.0 引入的一条命令
- 可以提升包内提供的命令行工具的使用体验
- 启动项目
npm start
3.2在脚手架中使用React
-
导入react 和 react-dom两个包
import React from 'react' import ReactDOM from 'react-dom'
-
调用 React.createElement()方法创建react元素
-
调用 ReactDOM.render()方法渲染元素到页面中
二、JSX
1.JSX的基本使用
1.1 createElement()的问题
- 繁琐不简洁
- 不直观,不能从代码中轻易的看出结构
- 不优雅
1.2 JSX简介
- JSX 是 JavaScript XML的简写,表示在JavaScript代码中写**XML(HTML)**格式的代码。
- 优势:
- 声明式语法更加直观
- 与HTML结构相同,降低了学习成本
- 提升开发效率
1.3使用步骤
-
使用JSX语法创建react元素
const title = ( <h1>HelloWorld!</h1> )
-
渲染元素 ReactDOM.render()
1.4为什么React脚手架中可以使用JSX语法
- JSX不是标准的ECMAScript语法,它是ECMAScript的语法扩展
- 需要使用babel编译处理后,才能在浏览器环境中使用
- React脚手架中默认已经帮我们配置好了babel相关的配置,无需手动配置
- 编译JSX语法的包为:@babel/preset-react
1.5注意事项
- React元素的属性名使用驼峰命名法
- class —> className for —> htmlFor tabindex —> tabIndex
- 没有子节点的React元素可以用 />结束
- 推荐 :使用小括号包裹JSX,从而避免JS中自动插入分号陷阱
2.JSX中使用JavaScript表达式
const test='haha'
const title = (
<h1>HelloWorld! {test}</h1>
)
ReactDOM.render(title,document.getElementById('root'))
3.JSX的条件渲染
const show=(n)=>{
if(n===1) return <p>n是1</p>
else return <p>n不是1</p>
}
const title = (
<div>
<h2>{show(1)}</h2>
</div>
)
ReactDOM.render(title,document.getElementById('root'))
4.JSX的列表渲染
const p = [
{name:'张三',age:'18'},
{name:'罗老师',age:'不知道'}
]
const test = (
<div>
<ul>
{p.map(item=> <li>{item.name}---{item.age}</li>)}
</ul>
</div>
)
ReactDOM.render(test,document.getElementById('root'))
5.JSX的样式处理
import './index.css'
const test = (
<div>
//行内样式
<p style={{color:"red", fontSize:"30px"}}>你好</p>
//类名
<p className='test'>你好</p>
</div>
)
ReactDOM.render(test,document.getElementById('root'))
三、组件基础
1.React组件介绍
- 非常重要
- 组件表示页面中的部分功能
- 组合多个组件实现完整的页面功能
- 特点
- 可复用
- 独立
- 可组合
2.React组件的两种创建方式
-
使用函数创建组件
-
函数组件:使用函数(普通函数,箭头函数均可)创建的组件
-
约定1:函数名称必须以大写字母开头
-
约定2:函数组件必须有返回值,表示该组件结构
-
如果返回值为null,表示不渲染任何内容
-
//创建一个组件 const Test = ()=>( <div> <p style={{color:"red", fontSize:"30px"}}>你好</p> <p>你好</p> </div> ) //渲染组件,以函数名为标签名 ReactDOM.render(<Test/>,document.getElementById('root'))
-
-
使用类创建组件
-
类组件:使用ES6中的class创建的组件
-
约定1:类名称首字母大写
-
约定2:类组件应该继承React.Component父类,从而可以使用父类中提供的方法或者属性
-
约定3:类组件必须提供**render()**方法
-
约定4:render()方法必须有返回值,表示该组件结构
-
//创建一个类组件 class Test extends React.Component { render(){ return ( <div> <p style={{color:"red", fontSize:"30px"}}>你好</p> <p>你好</p> </div> ) } } //渲染组件,以函数名为标签名 ReactDOM.render(<Test/>,document.getElementById('root'))
-
-
抽离组件到单独js文件中
-
创建js文件
-
在文件中导入React
-
创建组件
-
暴露组件
-
//在src/components/Test.js文件中 import React from 'react' //创建并暴露一个组件 export default class Test extends React.Component { render(){ return ( <div> <p style={{color:"red", fontSize:"30px"}}>你好</p> <p>你好</p> </div> ) } } //在index.js文件中 import Test from './components/Test' ReactDOM.render(<Test/>,document.getElementById('root'))
-
3.React事件处理
-
语法:on+事件名称={事件处理函数}
-
注意:React事件采用驼峰命名法,如:onMouseEnter,onFocus
-
export default class Test extends React.Component { render(){ return ( <div> <button onClick={this.testClick}>点我一下</button> </div> ) } testClick(){ alert('点我干嘛!') } }
-
事件对象
-
可以通过事件处理程序的参数获取到事件对象
-
React中的事件对象叫做:合成事件(对象)
- 合成事件:兼容所有浏览器,无需担心跨浏览器兼容问题
-
export default class Test extends React.Component { render(){ return ( <div> <a onClick={this.testClick} href='https://www.baidu.com'>点我一下</a> </div> ) } testClick(e){ alert('点我干嘛!') // 阻止默认行为 e.preventDefault() } }
-
4.有状态组件和无状态组件
- 函数组件 ====> 无状态组件
- 函数组件没有自己的状态,只负责数据展示(静态)
- 类组件 ====> 有状态组件
- 类组件有自己的状态,负责更新UI(动态)
5.组件中的state和setState()
-
state即数据,是组件内部私有的
-
state是一个对象
-
export default class Test extends React.Component { // constructor(){ // super() // //初始化state // this.state={ // n:0 // } // } //简写 state={ n:0 } render(){ return ( <div> <h1>n:{this.state.n}</h1> <button onClick={this.add}>点我n加一</button> </div> ) } //用箭头函数,this才指向Test这个类!!!!!!!! add=()=>{ //直接更改(this.state.n++)可以改变n的值,但是不会导致页面更新,需要使用setState()方法 this.setState({n:this.state.n+1}) } }
6.事件绑定this指向
-
箭头函数
-
Function.prototype.bind()方法
constructor(){ super() this.test=this.test.bind(this) }
-
class的实例方法
- 利用箭头函数形式的class实例方法
//用箭头函数,this才指向Test这个类!!!!!!!! add=()=>{ //直接更改(this.state.n++)可以改变n的值,但是不会导致页面更新,需要使用setState()方法 this.setState({n:this.state.n+1}) }
7.表单处理
7.1受控组件
-
将表单元素的value值交给state管理
-
受控组件:其值受到React控制的表单元素
-
使用
export default class Test extends React.Component { state={ mes:'' } render(){ return ( <div> <input type="text" value={this.state.mes} onChange={e => this.setState({mes:e.target.value})}/> </div> ) } }
-
多个表单优化
export default class Test extends React.Component { state={ mes:'', isChecked:false } render(){ return ( <div> {/* 为表单元素添加name属性,值为state中的key值 */} <input type="text" name="mes" value={this.state.mes} onChange={this.change}/> <br/> 同意:<input type="checkbox" name="isChecked" checked={this.state.isChecked} onChange={this.change}/> </div> ) } //事件处理函数 change=e=>{ let newvalue //特殊处理复选框 if(e.target.type==='checkbox')newvalue=e.target.checked else newvalue=e.target.value this.setState({[e.target.name]:newvalue}) } }
7.2非受控组件(DOM方式)(不推荐)
-
借助ref,使用原生方式获取表单元素值
export default class Test extends React.Component { constructor(){ super() //创建ref this.mesRef=React.createRef() } render(){ return ( <div> <input type="text" ref={this.mesRef}/><button onClick={this.getMes}>点我</button> </div> ) } //获取文本框值 getMes=()=>{ alert(this.mesRef.current.value) } }
四、组件进阶
1.组件的props
-
组件是封闭的,要接收外部数据应该通过props来实现
-
props的作用:接收传递给组件的数据
-
传递数据:给组件标签添加属性
render( return ( <Test data='xxx'/> //通过属性传递数据 ) )
-
接收数据:
-
函数组件通过参数props接收数据
function Test(props){ console.log(props.data) return (...) }
-
类组件通过this.props接收数据
class Test extends React.Component { render(){ console.log(this.props.data) return (...) } }
-
-
注意!!!!!!!!!
-
通过props可以传递任意类型数据
-
props的值是只读的
-
使用类组件时,如果写了构造函数,应该将props传递个super(),否则,无法在构造函数中取到props!!!
class Test extends React.Component{ constructor(props){ super(props) ... } }
-
2.组件通信的三种方式
- 父组件 ==> 子组件
- 父组件提供要传递的数据(state)
- 给子组件标签添加属性,值为state中的数据
- 子组件通过props接收父组件传递的数据
- 子组件 ==> 父组件
- 父组件向子组件传递一个回调函数,回调函数的参数为需要接收的数据
- 子组件调用父组件传递的回调函数,将数据传递给父组件
- 兄弟组件间
- 状态提升:将共享的状态(即数据)提升到最近的公共父组件中,由公共的父组件管理这个状态
- 公共的父组件提供数据及操作数据的方法
- 子组件只需要通过接收数据或者调用方法去传递或者接收数据
3.Context
-
作用:跨组件传递数据
-
使用
-
调用 React.createContext()创建Provider(提供数据) 和Consumer(消费数据)两个组件
const {Provider,Consumer}=React.createContext()
-
使用Provider组件作为父节点
<Provider value={data}> {/*将要传递的数据通过Provider标签的value属性传递*/} <div className="App"> <Child1/> </div> </Provider>
-
使用Consumer组件接收数据
<Consumer> {data => <p>{data}</p>} {/*接收数据*/} </Consumer>
-
4.props深入
-
children属性:表示组件标签的子节点。当前组件标签有子节点时,props就会有该属性
-
props校验:允许在创建组件的时候,就指定props的类型、格式等
-
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
-
使用:
-
安装 prop-types
npm i props-types
-
导入
import PropTypes from 'prop-types'
-
使用
组件名.propTypes={}
给组件的props添加校验规则Test.propTypes={ test:PropTypes.array }
-
-
约束规则:
- 常见类型:array\bool\func\number\object\string
- React 元素类型:element
- 必填项:isRequired
- 特定结构的对象:shape({})
//必选 test:PropTypes.类型.isRequired //特定结构对象 test:PropTypes.shape({ testc1:PropTypes.类型 testc2:PropTypes.类型 ... })
-
-
props默认值
组件.defaultProps={}
5.组件的生命周期
5.1概述
- 意义:有助于理解组件的运行方式、完成更加复杂的组件功能、分析组件错误原因等
- 组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不再用时卸载的过程
- 生命周期钩子:生命周期中某一个重要阶段会有一些方法的调用,这些方法就叫做生命周期钩子
- 生命周期钩子,为开发人员在不同阶段操作组件提供了时机
- 只有类组件才有生命周期!
5.2生命周期的三个阶段
-
在Commit阶段要避免调用setState等更新组件,如果这样会造成无限递归循化。
-
在componentDidUpdate钩子中使用setState方法要添加限制条件
//一般是比较当前props与上一次的props是否一样 componentDidUpdate(prevProps){//可以通过该参数获取到上一次的props if(prevProps.data!==this.props.data){ this.setState({...}) } }
-
6.render-props和高阶组件
-
render props模式
-
在使用一个组件时,添加一个值为函数的prop,通过函数参数来获取复用组件中的数据(需要组件内部实现)
-
使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)
-
使用
-
创建复用组件,在组件中提供复用的状态及操作状态的方法
class TestA extends React.Component{ state={ ... } //操作数据方法 ... }
-
在需要组件中定义回调函数接收相应的状态并返回相应的结构
class App extends React.Component{ fun=data=>{ return (...) //结构 } //将该回调传递给复用组件 render(){ // 这里通常用children属性传递 return( <TestA children={this.fun}/> ) } }
-
在复用组件的render方法中调用props中接收到的render方法
class TestA extends React.Component{ ... //推荐为children添加校验 propTypes={ children:PropTypes.func.isRequired } render(){ return this.props.children(this.state.data) } ... }
-
-
-
高阶组件(HOC)
-
原理:装饰模式
-
高阶组件(HOC ,Higher-Order Component)是一个函数,接收要包装的组件,返回增强后的组件
-
高阶组件内部创建一个类组件,在这个组件中提供复用的状态逻辑代码,通过props将复用的状态传递给被包裹的组件
-
使用
-
创建一个函数,名称约定以with开头
-
指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
-
在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
-
在该组件中,渲染参数组件,同时将状态通过props传递给参数组件
function withTest(Example){ class Test extends React.Component{ state={ data } //操作数据方法 ... render(){ return ( //通过children属性传递数据 <Example children={...this.state.data}/> ) } } return Test //返回装饰后的组件 } //在需要组件中 class Father extends React.Component{ ... //推荐为children添加校验 propTypes={ children:PropTypes.func.isRequired } render(){ return (...) //结构,其中使用props中的数据 } } //调用高阶组件,装饰需要组件 const FatherPlus = withTest(App) //在其他组件中渲染装饰后的需要组件 <FatherPlus/>
-
推荐为高阶组件设置displayName属性,便于在开发者工具等调试
//使用高阶组件存在问题:得到的两个组件名称相同 //原因:默认情况下,React使用组件名称作为displayName //为高阶组件设置displayName属性 Test.displayName=`WithTest${getDisplayName(Example)}` function getDisplayName(Component){ return Component.displayName || Component.name || 'Component' }
-
推荐高阶组件往下传递props
//问题:props丢失 //原因:高阶组件没有向下传递props //解决方式,在高阶组件中渲染需要组件参数时,将state和this.props一起向下传递 <Example children={...this.state.data} {...this.props}/>
-
-
五、React原理
1.setState()说明
-
setState更新数据是异步的!!!!!!!!!!!
-
避免更新数据直接依赖上次更新后的数据
-
连续多次调用setState(),只会触发一次页面重新渲染
-
推荐语法:setState((state,props)=>{})
//这里的state永远是最新的state,但是仍然是异步的 this.setState((state,props)=>{ return { } })
-
setState有第二个参数,参数是一个回调函数,这个函数会在状态更新完成后立即执行。
2.JSX语法的转换过程
- JSX实际上是createElement()方法的语法糖
- JSX被@babel/preset-react插件编译为createElement()
- createElement()会把内容转化为React元素(是一个对象,用来描述界面内容)
3.组件更新机制
- 过程:父组件更新,会更新其组件子树
4.组件性能优化
-
减轻state:只存储跟组件相关的数据
-
避免不必要的重新渲染
- 使用钩子函数 shouldComponentUpdate(nextProps,nextState) 返回值为bool,true:需要更新,flase:不需要更新
-
纯组件(pureComponent)
-
纯组件内部自动实现了shouldComponentUpdate钩子,不需要手动进行比较
-
纯组件内部的对比是shallow compare(浅层对比)
- 对于值类型没有影响
- 对于引用类型,浅层对比只比较对象的引用是否相同(解决方式:创建新对象)
-
5.虚拟DOM和Diff算法
- 只要状态改变,就重新更新视图
- 虚拟DOM:本质上就是JS对象,用来描述你希望在屏幕上看到的内容(UI)
- Diff算法:
- 初次渲染时,React会根据state(Model),创建一个虚拟DOM对象(树)
- 根据虚拟DOM生成真正的真实DOM,渲染到页面中
- 当状态发送改变的时候,更新新的数据,创建新的虚拟DOM对象(树)
- 根据diff算法,找到需要更新的地方,更新真实DOM
六、React路由
1.路由的基本使用
-
安装 react-router-dom :
npm i react-router-dom
-
在index.js中引入BrowserRouter
import {BrowserRouter} from 'react-router-dom'
-
用BrowserRouter组件将整个应用包裹起来
<BrowserRouter> <App /> </BrowserRouter>
- 两种常用的Router:
- HashRouter:使用URL的哈希值实现 (不美观,不优雅,有/#/)
- BrowserRouter:使用H5的historyAPI实现
- 两种常用的Router:
-
引入Link ,Routes,Route
import { Link,Routes,Route } from "react-router-dom"
-
添加Link
<Link to="/test">test</Link>
(最终会解析成a标签)-
补充 NavLink组件,可以通过isActive实现显示路由的样式
import { NavLink } from "react-router-dom"; <NavLink className={({ isActive }) => isActive ? "red" : "blue"} />
-
-
添加Routes及Route
<Routes> <Route path="/" element={<App />} /> {/*path为'/'表示默认路由*/} <Route path="/test" element={<Test />} /> <Route path="*" {/*path为'*'表示其它未定义路由404 No Found*/} element={ <main style={{ padding: "1rem" }}> <p>404 No Found!</p> </main> } /> </Routes>
2.路由嵌套
<Routes>
<Route path="/" element={<App />}>
<Route index path="expenses" element={<Expenses />} /> {/*index 属性,让子级路由设为默认路由*/}
<Route path="invoices" element={<Invoices />} />
</Route>
</Routes>
//自适应渲染组件Outlet
//在App组件中
export default function App() {
return (
<div>
<nav>
<Link to="/invoices">Invoices</Link>
<Link to="/expenses">Expenses</Link>
</nav>
<Outlet /> {/*根据路径决定渲染哪个组件*/}
</div>
);
}
3.路由传参
3.1 useParams
-
通过path传递参数
<Route path=":invoiceId" element={<Invoice />} />
-
接收参数
import { useParams } from "react-router-dom" export default function Invoice() { let params = useParams() return <h2>Invoice: {params.invoiceId}</h2> }
3.2 useSearchParams
-
传递参数
<Route path="/test?id=1&num=2" element={<Test />} />
-
接收参数
import { useSearchParams } from "react-router-dom" export default function Test() { let [searchParams, setSearchParams] = useSearchParams(); let id=searchParams.get('id') let num=searchParams.get('num') //gitAll方法也可以用于获取,但是麻烦,不推荐 //let id=searchParams.getAll('id')[0] } //补充:setSearchParams() 参数为一个对象,用于设置URL setSearchParams({id:1,num:2}) //可以将路径设为当前路径下 '/test?id=1&num=2'
4.编程式路由及其传参
-
使用useNavigate钩子进行路由跳转及传递参数
import {useNavigate} from 'react-router-dom' let navigate=useNavigate() const test =() =>{ navigate("/test",{state:{ //第一个参数表示路径,第二个表示参数,为一个对象,对象中的state属性携带参数 id:1, num:2 }}) }
-
使用useLocation接收参数
import {useLocation} from 'react-router-dom' let location=useLocation() let {id,num} = location.state
5.常用组件及钩子
组件名 | 作用 | 说明 |
---|---|---|
<Routers> | 一组路由 | 代替原有<Switch> ,所有子路由都用基础的Router children来表示 |
<Router> | 基础路由 | Router是可以嵌套的,解决原有V5中严格模式,后面与V5区别会详细介绍 |
<Link> | 导航组件 | 在实际页面中跳转使用 |
<Outlet/> | 自适应渲染组件 | 根据实际路由url自动选择组件 |
hooks名 | 作用 | 说明 |
---|---|---|
useParams | 返回当前参数 | 根据路径读取参数 |
useNavigate | 返回当前路由 | 代替原有V5中的 useHistory |
useOutlet | 返回根据路由生成的element | |
useLocation | 返回当前的location 对象 | |
useRoutes | 同Routers组件一样,只不过是在js中使用 | |
useSearchParams | 用来匹配URL中?后面的搜索参数 |