React小结
React是一个用于构建用户界面的 JavaScript 库,具有如下特点:
- a、声明式设计
react是面向数据编程,不需要直接去控制dom,你只要把数据操作好,react自己会去帮你操作dom,可以节省很多操作dom的代码。这就是声明式开发。 - b、使用JSX语法
JSX 是 JavaScript 语法的扩展。React 开发大部分使用 JSX 语法(在JSX中可以将HTML于JS混写)。 - c、灵活
react所控制的dom就是id为root的dom,页面上的其他dom元素你页可以使用jq等其他框架 。可以和其他库并存。 - d、使用虚拟DOM、高效
虚拟DOM其实质是一个JavaScript对象,当数据和状态发生了变化,都会被自动高效的同步到虚拟DOM中,最后再将仅变化的部分同步到DOM中(不需要整个DOM树重新渲染)。 - e、组件化开发
通过 React 构建组件,使得代码更加容易得到复用和维护,能够很好的应用在大项目的开发中。 - f、单向数据流
react是单向数据流,父组件传递给子组件的数据,子组件能够使用,但是不能直接通过this.props修改。 这样让数据清晰代码容易维护。
React的基本使用
1.使用 create-react-app 脚手架创建项目
npx create-react-app 项目名 或者 yarn create react-app 项目名(npx 是一个临时使用第三方模块的命令,会临时下载这个包,使用完毕就删除了)
2.项目结构
3.启动项目
我们只需要在项目根目录下使用 npm start 或者 yarn start 就可以启动项目。
react17的ReactDOM.render()用法
import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.render((
<h1>Hello World</h1>
),
document.getElementById('root')
)
react18的ReactDOM.createRoot的用法。
import React from 'react';
import ReactDOM from 'react-dom/client';
const rootEl = document.getElementById('root')
const root = ReactDOM.createRoot(rootEl)
root.render(<h1>Hello World</h1>)
JSX语法
1.了解JSX
JSX其实就是在JS里面写XML(HTML),在React里面使用JSX来代替常规的JS。
我们不需要一定使用 JSX,但它有以下优点:
JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
它是类型安全的,在编译过程中就能发现错误。
使用 JSX 编写模板更加简单快速。
通常来说,在React项目里面,无论是.js文件还是.jsx文件,都可以写JSX语法。也就是说无论是.js还是.jsx都是React的组件,所以当我们创建一个组件文件时,写.js或者.jsx都是可以的。但是推荐使用.jsx,这样可以更加明确这是一个jsx的组件。
-
1.在jsx里面可以html和js混写
-
2.在js里面写html要加小括号
-
3.在html里面写js要加大括号
-
4.js/jsx/tsx文件里面都可以写jsx语法
1.class组件
在早期的react用法里面,主要是class组件,但是因为这种方式量级比较重,以及使用起来比较繁琐,后期维护难度比较高,所以在后期的项目中已经基本不使用了。但是我们还是要知道这种写法。
class组件需要我们继承自react提供的一个Component类,并在里面提供一个render方法,来返回一个jsx结构。
定义一个class组件的基本格式如下。
import { Component } from "react";
export default class ComClass extends Component {
render() {
return <div>ComClass</div>;
}
}
2.函数式组件
react16.8开始,因为hook的推出,极大地提升了函数式组件的实用性,所以基本在外面的项目中,都全面转型到了 FC + Hook 的用法。
函数式组件只有一个函数,需要返回一个jsx结构。相比于class组件而言,非常地轻量。
定义一个函数组件的基本格式如下:
export default function ComFun() {
return <div>ComFun</div>;
}
生命周期
class组件里面的9个生命周期函数。
React的生命周期指从组件被创建到销毁的过程。掌握了组件的生命周期,就可以在适当的时候去做一些事情。
React生命周期可以分成三个阶段:
-
1、实例化(挂载阶段):对象创建到完全渲染
-
2、存在期(更新期):组件状态的改变
-
3、销毁/清除期:组件使用完毕后,或者不需要存在与页面中,那么将组件移除,执行销毁。
1、实例化/挂载阶段
-
constructor()
-
componentWillMount()
-
render()
-
componentDidMount()
export default class App3 extends Component {
// 生命周期第一个阶段: 挂载/初始化阶段
constructor(props){
console.log("1.1 constructor: 构造初始化")
}
UNSAFE_componentWillMount(){
console.log("1.2 componentWillMount")
//做一些准备性的工作,比如提示正在加载
}
componentDidMount() {
console.log("1.4 componentDidMount")
//异步加载数据
}
render() {
console.log("1.3 render")
return (
<div>
<p>这是一个展示生命周期的组件</p>
</div>
)
}
}
2、存在期/更新期
-
componentWillReceiveProps()
-
shouldComponentUpdate()
-
componentWillUpdate()
-
render()
-
componentDidUpdate()
constructor(){
this.state = {
num:0
}
}
render() {
console.log("1.3/2.4 render")
return (
<div>
<p onClick={this.handleClick.bind(this)}>这是一个展示生命周期的组件{this.state.num}</p>
</div>
)
}
handleClick(){
this.setState({
num:22
})
}
// 生命周期第二个阶段 存在期/更新期
componentWillReceiveProps(){
console.log("2.1 componentWillReceiveProps")
}
shouldComponentUpdate(nextProps, nextState) {
console.log("2.2 shouldComponentUpdate 可以判断修改前后的值是不是一样,不一样才去执行render。减少不必要的render,优化更新性能")
console.log("旧的值:", this.state.num)
console.log("新的值:", nextState.num)
// return true 则执行render
// return false 则不执行render
//这里返回值是布尔值,但不应该写死,
//而是判断新旧两个值是不是不相等,不相等就要执行render(就要返回true)
return this.state.num !== nextState.num
}
componentWillUpdate(nextProps, nextState) {
console.log("2.3 componentWillUpdate 更新前的生命周期回调")
}
componentDidUpdate(prevProps, prevState) {
console.log("2.5 componentDidUpdate 更新后的生命周期回调")
}
以上执行的是组件内部state数据更新前后的生命周期函数,
其实,对于组件的props属性值发生改变的时候,同样需要更新视图,执行render
componentWillReceiveProps() 这个方法是将要接收新的props值的时候执行,而props属性值从父组件而来,所以需要定义父组件:
class App3 extends Component {
//生命周期第二个阶段 存在期/更新期
UNSAFE_componentWillReceiveProps(nextProps){
console.log("2.1 componentWillReceiveProps 这个方法props属性值更新的时候才会执行,更新state数据则不会执行这个方法")
console.log(nextProps)
}
shouldComponentUpdate(nextProps, nextState) {
console.log("2.2 shouldComponentUpdate 可以判断修改前后的值是不是一样,不一样才去执行render。减少不必要的render,优化更新性能")
//而是判断新旧两个值是不是不相等,不相等就要执行render(就要返回true)
return (this.state.num !== nextState.num || this.props.fatherNum !== nextProps.fatherNum) //不仅仅是state数据跟新时候需要执行render,props属性的值更新的时候也要执行render,所以要多加一个判断条件
}
}
export default class Father extends Component{
constructor(props){
// 调用父类构造方法
super(props)
// 初始化状态数据
this.state = {
fatherNum:0
}
}
componentDidMount() {
setTimeout(() => {
this.setState({
fatherNum:10
})
}, 2000);
}
render(){
return(
<App3 fatherNum={this.state.fatherNum}/>
)
}
}
3、销毁期
- componentWillUnmount() 销毁组件前做一些回收的操作
componentDidMount() {
console.log("1.4 componentDidMount")
document.addEventListener("click", this.closeMenu);
}
closeMenu(){
console.log("click事件 closeMenu")
}
// 生命周期第三个阶段 卸载期/销毁期
componentWillUnmount() {
console.log("3.1 componentWillUnmount 做一些回收的操作")
document.removeEventListener("click", this.closeMenu);
}
- index.js中3秒后重新渲染页面,就能触发组件的回收。
setTimeout(() => {
ReactDOM.render(
<div>hello world</div>
, document.getElementById('root'));
}, 3000);
4、生命周期小结
React组件的生命周期 3大阶段10个方法 1、初始化期(执行1次) 2、存在期 (执行N次) 3、销毁期 (执行1次)
-
componentDidMount : 发送ajax异步请求
-
shouldComponentUpdate : 判断props或者state是否改变,目的:优化更新性能
Hooks(注意的是,hook只能使用在函数的最外层,任何别的位置都会报错。)
- 1.useState实现函数式组件的响应式数据。 (StateHook)
这是一个react提供好的用于实现state响应式特性的hook,使用如下:
export default function StateHook() {
// 调用useState得到一个数组,顺便解构出来两个东西,一个是数据,一个是用于操作数据的函数
const [count, setCount] = useState(0);
const onClick = () => {
// 调用对应的方法操作数据
setCount(count + 1);
};
return (
<div>
{/* 直接渲染对应的数据 */}
<p>Count:{count}</p>
<button onClick={onClick}>增加</button>
</div>
);
}
- 2.useContext实现多级组件间的数据传递。(ContextHook)
ContextHook是用来解决多级组件之间数据传递的问题的。首先必须明确的是,函数式组件之间的父子数据传递数据,几乎和class组件是一样的。
//a.调用createContext创建一个Context对象
const MyContext = createContext();
//b.使用Provider组件提供一个“全局”数据
export default function ContextHook() {
const [count, setCount] = useState(10);
const add = (val) => {
setCount(count + val);
};
return (
<MyContext.Provider value={{ count, add }}>
<Parent />
</MyContext.Provider>
);
}
//c.在后代组件里面使用useContext方法获取Context对象
function Parent() {
return (
<div>
<Child />
</div>
);
}
function Child() {
const context = useContext(MyContext);
return (
<div>
{/* context对象就是之前的Provider里面的value属性 */}
<p>count from ancestors:{context.count}</p>
<button onClick={() => context.add(1)}>Add</button>
</div>
);
}
- 3.useEffect模拟生命周期。(EffectHook)
函数组件里面没有生命周期,我们可以使用EffectHook来模拟
useEffect的基本用法如下:
//useEffect(当依赖发生变化时调用的函数, [依赖的数据]);
//使用EffectHook模拟生命周期:
//a.模拟componentDidMount
useEffect(() => {
console.log("这是用来模拟挂载期的");
}, []);
//b.模拟componentDidUpdate
useEffect(() => {
console.log("这是用来模拟更新期的");
}, [deps]);
//c.模拟componentWillUnmount
useEffect(() => {
return ()=>{
// 在这里面写卸载期的代码
console.log("这是用来模拟卸载期的");
}
}, []);
注意:
同个useEffect下,在检测销毁和检测字段更新之间,只能二选一。留下空数组,可以检测到return中的销毁。数组非空时,视图更新会带动return返回值,因此如果要检测字段更新,就无法检测销毁
- 4.useRef获取DOM元素的引用。(RefHook)
在函数组件里面我们可以使用userRef这个Hook获取一个元素或者组件的引用
import React, { useEffect, useRef } from "react";
export default function RefHook() {
const btn = useRef();
// 在componentDidMount后获取
useEffect(() => {
console.log(btn.current);
}, []);
return (
<div>
<button ref={btn}>Button</button>
</div>
);
}
- 5.useReducer实现响应式数据操作 (ReducerHook)
useReducer是useState的替代方案,当useState里面的逻辑相对复杂的时候,我们可以使用useReducer来代替。
useRducer的基本使用步骤如下
//a.准备一个初始state数据和操作state的方法
// 初始state
const state = {
count: 0,
};
// 操作state的方法reducer
// aciont里面一般是type和pyload两个属性用来判断不同的复杂逻辑
const reducer = (st = state, action) => {
const state = JSON.parse(JSON.stringify(st));
// 通过判断不同的action来处理不同的逻辑
switch (action.type) {
case "add":
state.count += action.pyload;
break;
case "reduce":
state.count -= action.pyload;
break;
}
// reducer一定要返回一个新的state
return state;
};
//b.调用useReducer这个Hook来得到state和dispatch
export default function ReducerHook() {
// state就是我们需要维护的数据,dispatch是一个方法,用来调用reducer的
const [state, dispatch] = useReducer(reducer, initState);
return (
<div>
<div>ReducerHook</div>
<div>state's count : {state.count}</div>
{/* dispatch方法传入一个action来激活reducer里面对应的操作 */}
<button onClick={() => dispatch({ type: "add", pyload: 1 })}>Add</button>
<button onClick={() => dispatch({ type: "reduce", pyload: 1 })}>
reduce
</button>
</div>
);
}
6.自定义hook
-
使用hook可以让我们的数据的操作数据的逻辑放在一起,并且可以实现封装,把代码整理地更加易于维护。
-
如果在操作数据的过程中有一些比较复杂的逻辑,我们就可以采用自定义hook的方式把这部分逻辑分离出来。
// 自定义hook要求以use开头
function useCount(initValue = 0) {
const [count, setCount] = useState(initValue);
const fn = () => {
let val = 0;
if (count > 0) {
val = count - 1;
} else {
val = count + Math.floor(Math.random() * 10);
}
setCount(val);
};
return [count, fn];
}
export default function StateHook() {
// 调用我们自定义的hook , 把复杂的逻辑全抽离到自定义hook,这样在我们的组件里面就尽可能简洁了
const [count, setCount] = useCount();
const onClick = () => {
setCount();
};
return (
<div>
<p>Count:{count}</p>
<button onClick={onClick}>增加</button>
</div>
);
}
路由
小节
1.使用react-router-dom进行组件的切换。
2.使用Outlet组件并配置子路由
3.使用useNavagate进行手动跳转。
4.使用路由进行数据传递(三种方式)。
1.下载安装
npm i --save react-router-dom@6 或 yarn add react-router-dom@6 --save
2.基本使用
// 1. 导入 reactrouter提供的组件
import { BrowserRouter, Routes, Route } from "react-router-dom";
export default function BasicRouter() {
return (
// 2.确定使用的是哪种路由模式(history/hash)
<BrowserRouter>
{/* 3.配置路由表 */}
<Routes>
{/* 4.配置每个路由path对应的组件 */}
<Route path="/" element={<Index />}></Route>
<Route path="/list" element={<List />}></Route>
<Route path="/about" element={<About />}></Route>
</Routes>
</BrowserRouter>
);
}
3.配置子路由
//a.直接在父路由里面配置子路由
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />}>
{/* 直接在父 Route下面配置子Route */}
<Route path="list" element={<List />}></Route>
<Route path="about" element={<About />}></Route>
</Route>
</Routes>
</BrowserRouter>
//b.在作为父路由对应的组件里面使用 Outlet 组件来展示子路由对应的组件
function Index() {
return (
<div>
<div>Index</div>
<Outlet />
</div>
);
}
4.使用Link作为跳转方式
//a.路由配置
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />}>
<Route path="list" element={<List />} />
<Route path="about" element={<About />} />
</Route>
</Routes>
</BrowserRouter>
//b.在父组件里面配置Link和Outlet
import { Link, Outlet } from "react-router-dom";
function Index() {
return (
<div>
<ul>
<li><Link to="/list">List</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
<Outlet />
</div>
);
}
5.编程式导航
ReactRouter v6.x 使用useNavigate这个hook来进行跳转。
function Index() {
// 1. 在组件里面使用 useNavigate 得到 navigate 函数
const nav = useNavigate();
return (
<div>
<div>Index</div>
{/* 2. 在需要跳转的位置调用 navigate 函数跳转,直接传入一个path、数字或者路由配置对象 */}
{/* 如果 navigate 函数接收的是一个数字, 负数表示后退,正数表示前进 */}
<button onClick={() => nav("/state")}>
about
</button>
</div>
);
}
6.路由传参
//a.动态路由形式
//首先进行路由表配置
<BrowserRouter>
<Routes>
<Route path="list/:id" element={<List />}></Route>
</Routes>
</BrowserRouter>
//然后在对应的组件里面使用useParams这个hook进行数据获取
import { useParams } from "react-router-dom";
function List() {
const params = useParams();
console.log(params); // {id:xxx}
return <div>List</div>;
}
//b.query参数
//如果有下面这样一个url
//http://www.domain.com/list?id=10&type=3
//如果想获取到 ? 之后的参数,我们需要使用 useSearchParams 这个hook进行操作
import { useSearchParams } from "react-router-dom";
function About(){
// 调用 useSearchParams 得到query参数
let [searchParams, setSearchParams] = useSearchParams();
// 调用 searchParams 对象的 get 方法得到对应的数据
console.log(searchParams.get('id')) // 10
console.log(searchParams.get('type')) // 3
return <div>About</div>
}
//c.state传参
//state传递数据的方式需要使用navigate手动跳转的时候传递。
//首先在A组件里面。
function Index() {
const nav = useNavigate();
return (
<div>
<div>Index</div>
{/* navigate 函数的第二个参数可以传递state数据 */}
<button onClick={() => nav("/state", { state: { id: 456 } })}>go to state page</button>
</div>
);
}
//然后在B组件里面使用location对象获取。
function State() {
const location = useLocation();
console.log(location.state); // { id:456 }
return <div>State</div>;
}
ReactRedux
使用react-redux进行全局数据共享(Provider,userSelector,useDisaptch)。
ReactRedux是一套React专用的全局状态管理工具,在大型项目中也非常常用。
1.安装
react-redux是基于redux这个包的,所以我们需要在安装react-redux的时候,把redux一起安装
npm i redux react-redux 或者 yarn add redux react-redux
2.基本使用
//a.使用redux提供的createStore方法创建一个store对象
import { createStore } from 'redux'
const defState = {
count: 100
}
const reducer = (state = defState, action) => {
state = JSON.parse(JSON.stringify(state))
switch (action.type) {
case 'add':
state.count += action.pyload;
break;
}
return state
}
// 创建store对象,createStore方法需要一个reducer函数
const store = createStore(reducer)
export default store;
//b.使用react-redux提供的Provider组件全局提供数据
import store from "./store.js";
import { Provider } from "react-redux";
export default function BasicStore() {
return (
// Provider 需要一个store属性,这个store属性就是我们的store对象
<Provider store={store}>
<div>BasicStore</div>
<Child />
</Provider>
);
}
//c.在Provider包着的后代组件里面使用 userSelector 和 useDisaptch 对store里面的数据进行操作
import { useDispatch, useSelector } from "react-redux";
function Child() {
// useSelector方法可以根据一个函数得到state里面的数据
const count = useSelector((state) => state.count);
// dispatch 是一个函数,用来触发 在 recuder 里面的 action 来操作数据
const dispatch = useDispatch();
return (
<div>
<div>Child</div>
<div>{count}</div>
{/* 直接调用 dispatch 触发 reducer 里面对应的 action */}
<button onClick={() => dispatch({ type: "add", pyload: 2 })}>Add</button>
</div>
);
}
3.connect组件使用redux
其实这种方式还算比较复杂,已经不推荐使用,但是如果是在class组件里面,因为我们没法使用hook,所以需要了解一下这些用法。
function Ch(props) {
return (
<div>
<div>Child</div>
{/* 直接使用props访问store里面的数据 */}
<div>{props.count}</div>
{/* 直接使用props访问store里面的方法 */}
<button onClick={props.add}>Add</button>
</div>
);
}
// 这个函数用于将store的数据映射到组件的props上
const mapStateToProps = (state) => ({ count: state.count });
// 这个函数用于将store的dispatch映射到组件的props上
const mapDispatchToProps = (dispatch) => ({
add() {
dispatch({ type: "add", pyload: 2 });
},
});
// 使用react-redux提供的 connect 方法将组件进行加工,将数据映射到props上
const Child = connect(mapStateToProps, mapDispatchToProps)(Ch);
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析