当前位置: 首页 > news >正文

宝山做手机网站建设营销有哪些基本内容

宝山做手机网站建设,营销有哪些基本内容,.net网站开发工具介绍,网络推广公司北京前言 个人笔记,记录个人过程,如有不对,敬请指出React17React HookTS4 最佳实践仿 Jira 企业级项目项目完成到第十章,剩下后面就没有看了,说的不是特别好 github地址:https://github.com/superBiuBiuMan/React-jira husky方便我们管理git hooks的工具 REST-API风格 https://zh…前言 个人笔记,记录个人过程,如有不对,敬请指出React17React HookTS4 最佳实践仿 Jira 企业级项目项目完成到第十章,剩下后面就没有看了,说的不是特别好 github地址:https://github.com/superBiuBiuMan/React-jira husky方便我们管理git hooks的工具 REST-API风格 https://zhuanlan.zhihu.com/p/536437382 json-server 安装 npm install -g json-server项目安装 npm install -D json-server 项目开始 用jsx渲染开发工程列表 初始化代码出现问题,表单收集的组件无法将请求结果发送给list组件 import React, { useEffect, useState } from react;const List () {const [params,setParams] useState({name:,personId:,});const [selectOptions,setSelectOptions] useState([])const [listData,setListData] useState([]) //请求列表数据useEffect(async () {try{const response await fetch(/abc);if(response.ok){const result await response.json();//获取数据结果setListData(result ?? [])}}catch (e){console.log(发生错误);}},[params])return (forminput typetext value{params.name} onChange{event setParams({ ...params,name:event.target.value })}/select value{params.personId} onChange{event setParams({...params,personId: event.target.value,})}{selectOptions.map(item {return (option value{item.value}{ item.label }/option)})}/select/form); };export default List; 解决办法(3-2 用状态提升分享组件状态完成工程列表页面),也就是将请求放在父组件当中顺带一提,如果我们使用的是vite创建的react项目,就无法像老师一样直接使用process.env来读取设置的变量了 学习自定义hook useHooks(不管是自带的hooks还是自己创建的hooks),不可以在普通函数中运行,只能在hooks当中使用或者其他hooks使用,所以在自定义hooks的时候,需要以useXxx开头 使用自定义的useMount和useDebounce useMount /*只在初次挂载执行*/ export const useMount (callback) {useEffect(callback,[]) }useDebounce(防抖) /*自定义防抖hooks*/ export const useDebounce (value,delay) {const [debounceValue,setDebounceValue] useState(value)useEffect(() {const timer setTimeout(() { setDebounceValue(value) },delay);return () {/*下一次effect执行前的处理*/clearTimeout(timer)}},[value,delay])return debounceValue; } useDebounce的使用 /*搜索参数*/ const [params,setParams] useState({name:,personId:, }) const debounceValue useDebounce(params,2000);/** 当搜索条件发生变化的时候,就更新* */ useEffect(async () {/*请求获取列表数据*/try{const response await fetch(${apiUrl}/projects?${qs.stringify(cleanEmptyObj(debounceValue))})if(response.ok){const result await response.json();setListData(result)}}catch (e){console.log(e);} },[debounceValue])useDebounce的理解 传入: 传入需要节流的值和延迟 返回: 返回节流后的新state数据 原理: 内部对传入的value进行重新构建一个state,当传入的value发生改变的时候的时候,会被内部debounce创建的节流函数所捕捉,捕捉到后,如果中途没有重新捕捉到新的值,则会在设置的时间之后更新内部debounce的值,否则的话就会中断更新,重新计时 图示原理 js改造为ts 文件名更改 js - 改为 tsjsx - 改为tsx 遇到qs模块types缺失的情况: Could not find a declaration file for module qs.xxxx,npm i --save-dev types/qs,安装对应的types即可 yarn add types/qs -D 注意箭头函数和普通函数的泛型书写位置 // 箭头函数 const fn1 T,U() {}//普通函数 function fn2T,U() {}鸭子类型和json-server中间件 鸭子类型 ts是只看是否实现了这个接口当中的成员,实现了就可以通过,没有实现就不通过,不管有没有定义说通俗点就是只要你符合这个规定里面的规则,就是他说的一个东西比如鸭子会嘎嘎叫,只要你会嘎嘎叫,ts就认为你是鸭子 interface Base {id:number } const test (param:Base) {} /*定义一个对象,对象当中的实现了接口Base的成员*/ const a {id:100,name:李白 } test(a);//不会报错/*定义一个对象,对象当中没有实现Base的成员*/ const b {name:李白 } test(b);//警告 id is declared here. json-server中间件 注意POST为大写package.json更改json-server: json-server __json_server_mock/db.json --watch --port 3033 --middlewares __json_server_mock/middleware.js module.exports (req,res,next) {if(req.method POST req.path /login){if(req.body.username qiuye req.body.password 123456){return res.status(200).json({user:{token:我是token}})}/*密码错误*/else{return res.status(400).json({message:用户名或密码错误})}}next(); } 登录表单 import React, { FormEvent } from react; const apiUrl import.meta.env.VITE_REACT_APP_API_URL; const Login () {const login (params:{username:string,password:string}) {fetch(${apiUrl}/login,{method:POST,headers:{Content-Type:application/json},body:JSON.stringify(params),}).then(async (response) {if(response.ok){//await response.json();}})}/*点击登录*/const handleSubmit (event:FormEvent) {event.preventDefault();//阻止默认行为//ts-ignore;const username event.target[0].value;//ts-ignore;const password event.target[1].value;login({username,password})}return (form onSubmit{handleSubmit}label htmlForusername用户名/labelinput typetext idusername /br/label htmlForpassword密码/labelinput typepassword idpassword /button typesubmit登录/button/form); };export default Login; 安装jira-dev-tool npm地址:https://www.npmjs.com/package/jira-dev-tool 如果安装后项目启动数据库连接失败,可以试试运行这个命令 npx msw init public //或者 指定到public目录下 npx msw init ./public使用自定义useHttp处理登录状态 关于Parameters的使用,可以看看ts官方示例https://www.typescriptlang.org/docs/handbook/utility-types.html 所以老师当中的这个是什么意思呢? export const useHttp () {const {userInfo} useAuth();return (...[url,config]:Parameterstypeof http) http(url,{...config,token:userInfo.token}); }// 第一步: parameterstypeof http返回htpp的联合类型元组,也就是[url:string,{data,token,headers,...customConfig}:Config]//所以你明白老师为什么要设置为一个数组了吧?因为这个是联合类型元组,描述的是数组的结构// 第二步:使用扩展运算符展开函数参数//如果不适用展开运算符,我们调用函数必须要 Example([请求地址,config对象])//如果使用了,就可以 Example(请求地址,config对象)在使用函数的时候,如果是数组,想要将数组当中的数据依次传入数组,可以使用扩展运算符 const arr1 [1, -1, 0, 5, 3]; const min Math.min(...arr1); console.log(min);// -1 Math.min用法是传入0个或多个数字,0 个或多个数字将在其中选择并返回最小值。const arr1 [1, -1, 0, 5, 3]; const max Math.max(...arr1); console.log(max);// 5 Math.max用法是传入0个或多个数字,0 个或多个数字将在其中选择并返回最大值。更改为antd 表格排序的知识 localeCompare:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare string.localeCompare(targetString,locales,options);该方法返回的是一个数字用来表示一个参考字符串和对比字符串是排序在前在后或者相同返回值返回值是一个数字目前的主流浏览器都返回的是1、0、-1三个值但是也有其他情况所以不可以用绝对的值等于1、-1这种去判断返回的结果返回值大于0说明当前字符串string大于对比字符串targetString返回值小于0说明当前字符串string小于对比字符串targetString返回值等于0说明当前字符串string等于对比字符串targetString使用css-in-js-Emotion App.css样式更改为如下(App.css为全局样式) html{/*使得1rem 10px*/font-size: 62.5%;/* 这样子使得font-size为16px * 62.5% 10px */ }html body #root .App {min-height: 100vh; }安装emotion yarn add emotion/react emotion/styled安装编辑器插件 webstorm: Styled Components Styled JSX,不过最新版本的好像都自动安装了vscode: vscode-styled-components 使用emotion // html自带标签的使用 const Container styled.divdisplay: flex;flex-direction: column;align-items: center;min-height: 100vh;// 组件的使用 import {Card} from antd; const ShowCard styled(Card)box-sizing: border-box;width: 40rem;min-height: 56rem;padding: 3.2rem 4rem;border-radius: 0.3rem;box-shadow: 0 0 1rem rgba(0,0,0,.1); 注意不管有没有css样式后面必须要接一个模板字符串否者会报错 //不报错 const HeaderLeft styled(Row);//报错 const HeaderLeft styled(Row);设置多个背景(学到了,学到了,) background-image 属性用于为一个元素设置一个或者多个背景图像。background-position属性为每一个背景图片设置初位置 一个值为x,y设置相同的位置二个值分别为x轴位置和y轴位置 background-size属性设置背景图片大小 一个值: 指定图片的宽度,高度为auto二个值: 分别指定图片的高度 和 宽度逗号分割多个值,设置多重背景 /*设置背景图*/ background-repeat: no-repeat; background-position: left bottom, right bottom; background-size: calc(((100vw - 40rem)/2) - 3.2rem ) ,calc(((100vw - 40rem)/2) - 3.2rem ),cover; background-attachment: fixed; background-image: url(${LeftBg}),url(${RightBg});grid和flex各自的应用场景 一看空间 一般来说一维布局用flex二维布局用grid 二看内容和布局 从内容出发 有一组内容数量一般不固定然后希望他们均匀分布在容器在当中并且由内容自己的大小决定占据的空间**用flex**从布局出发先规划网格数量一般比较固定然后再把元素往里填充**用grid** css-in-js:Row组件实现 emotion允许我们像react一样传递参数来达到自定义样式的效果 Row组件样式当然可以不写ts在这里不过不写会有警告在tsx当中~ import styled from emotion/styled;export default styled.div{gap?: number | boolean,//右侧间距设置between?: boolean,//内容是否居中marginBottom?:number,//距离底部距离 }display: flex;align-items: center;justify-content: ${ props props.between ? space-between : undefined };margin-bottom: ${ props props.marginBottom ? props.marginBottom : 0 }; * {margin-top: 0!important;margin-bottom: 0!important;margin-right: ${ props typeof props.gap number ? props.gap rem : props.gap ? 2rem : undefined}}使用 import styled from emotion/styled; import Row from ../src/component/lib;const HeaderLeft styled(Row); // 传入对应的参数即可 HeaderLeft gap {true}h3Logo/h3h3Logo/h3h3Logo/h3 /HeaderLeft完成项目列表页面样式 使用emotion的css 在react当中我们可以直接对组件使用style设置样式但是不支持一些子元素选择符伪类等一些高级选择器的 MyComponent style{{ marginBottom:2rem }}/所以我们可以使用emotion/react来代替我们 /** jsx jsx */ import { jsx } from emotion/react; Form css{{marginBottom: 2rem}} /Form/老师是这样子写的但是我报错了 pragma and pragmaFrag cannot be set when runtime is automatic.不知道为什么就这样子吧后面遇到再说 图片以svg形式渲染 我们如果直接在React使用img去使用svg图片的时候并不能去设置svg参数了 import Logo from ../src/assets/svg/software-logo.svg; // 无法设置svg属性了 img src{Logo}/所以我们应该使用如下方式去使用svg图片通过组件的形式这样子我们就可以设置svg的一些参数了 import { ReactComponent as SoftwareLogo } from ../src/assets/svg/software-logo.svg;SoftwareLogo width{18rem} color{rgb(38,132,255)}/清除警告todo The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address a.... 原因a标签没有href导致的必须要一个合法的跳转链接不可以#,也不可以javascript:;解决替换为button,或者使用组件库设置button的属性为link React Hook useEffect has a missing dependency: callback. Either include it or remove the dependency array. 原因依赖项没有加入在依懒收集里面导致报错解决 useEffect(() {callback();// todo 依懒项里加上callback会造成无限循环这个和useCallback以及useMemo有关系},[])不要乱用object 覆盖范围很广比如一个箭头函数变量ts都认为这个是object export const isVoid (value:unknown) value undefined || value null || value ;/*清除空对象*/ export const cleanEmptyObj (obj: {[key:string]:unknown}) {const temp {...obj};Object.keys(temp).forEach(key {const value temp[key]if(isVoid(value)){delete temp[key]}})return temp; }Detected multiple renderers concurrently rendering the same context provider. This is currently unsupported.我这边失败了这个具体的就不加了 原因jira-dev-tool问题解决安装jira-dev-toolnext yarn add jira-dev-toolnextApp.tsc- import { loadDevTools } from jira-dev-tool;import { loadServer,DevTools } from jira-dev-tool;//原来写法 loadDevTools(() {ReactDOM.render(React.StrictModeAppProviderApp //AppProvider/React.StrictMode,document.getElementById(root)); });//更改为 loadServer(() {ReactDOM.render(React.StrictModeAppProviderDevTools/App //AppProvider/React.StrictMode,document.getElementById(root)); });登录注册页面loading和error状态处理 需要注意的是try-catch是同步的 为什么不能用useAsync的error,因为error更新是异步的,try-catch是同步的 const {run,isLoading,error} useAsync(undefined,{throwOnError: true});/*点击登录*/const handleSubmit async ({username,password}: {username:string,password:string}) {try {await run(login({ username, password }))}catch (e) {console.log(e)}};当try ... catch执行完成后,才开始执行error的更新函数,所以你会发现,第一次error输出没有值,第二次error就有了(第二次的error为上一次出错的值)未捕获错误(Uncaught Errors)-错误边界 react官网对于错误边界的说明 https://react.docschina.org/docs/error-boundaries.html 只有 class 组件才可以成为错误边界组件 react-error-boundary https://github.com/bvaughn/react-error-boundaryx 错误边界无法捕获以下场景中产生的错误 事件处理异步代码例如 setTimeout 或 requestAnimationFrame 回调函数服务端渲染它自身抛出来的错误并非它的子组件 老师创建的组件的要求 1.可以自定义错误显示的组件2.可以展示子组件当没有错误的时候 代码 import React, {Component, ErrorInfo, ReactNode} from react;export type Props {children:ReactNode,//子组件fallBackRender : (props: { error:Error | null }) React.ReactElement, }export interface State {error: Error | null; }export class Boundary extends ComponentProps, State {state {error:null}static getDerivedStateFromError(error:Error){return {error}}componentDidCatch(error:Error, errorInfo:ErrorInfo) {// 你同样可以将错误日志上报给服务器}render() {const { error } this.state;const { children, fallBackRender } this.props;if(error) return fallBackRender({error})return children;} } 使用useRef实现useDocumentTitle 需求 每一个组件可以使用useDocumentTitle方法,第一个参数传入新标题,第二个标题传入卸载组件后是否复原 疑问 为什么useDocumentTitle当中的设置document.title要放置在useEffect 因为在函数组件主体内这里指在 React 渲染阶段改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。说通俗点就是useEffect可以放置一些副作用操作在里面,并设置为依赖.而我们的document.title就是一个副作用,所以需要放置在useEffect别忘记了effect单词本身的意思哦useRef为什么要使用 /* 网页标题更改 */ export const useDocumentTitle (title:string,keepOnUnmount:boolean true) {//获取旧标题const oldTitle document.title;useEffect(() {document.title title;},[title])//更改新标题(副作用,使用需要使用useEffect)useEffect(() {return () {if(!keepOnUnmount) {//卸载的时候执行document.title oldTitle}}},[]); }//在上述代码中,因为闭包的问题,导致oldTitle可以保存最老的值//但是控制台可能会有警告,所以我们加入依赖,但是这又导致了oldTitle永远都是最新的值(也就是每执行一次,当前组件的oldTime都会被更新为上一次的值,而不是我们所想的指向最初始的title) export const useDocumentTitle (title:string,keepOnUnmount:boolean true) {//获取旧标题const oldTitle document.title;useEffect(() {document.title title;},[title])//更改新标题(副作用,使用需要使用useEffect)useEffect(() {return () {if(!keepOnUnmount) {//卸载的时候执行document.title oldTitle}}},[keepOnUnmount,oldTitle]); }所以我们可以借助useRef useRef 返回一个可变的 ref 对象其 .current 属性被初始化为传入的参数initialValue。返回的 ref 对象在组件的整个生命周期内持续存在。 //代码改造如下 export const useDocumentTitle (title:string,keepOnUnmount:boolean true) {//获取旧标题const oldTitle useRef(document.title).current;//更改新标题(副作用,使用需要使用useEffect)useEffect(() {document.title title;return () {if(!keepOnUnmount) {//卸载的时候执行document.title oldTitle}}},[title,keepOnUnmount,oldTitle]); } 动态改变网页标签显示的标题#todo 第一种 react-helmet 第二种 添加项目列表和项目详情 react-router-dom和react-router的关系 react-router管理路由关系,计算结果由react-router-dom来消费使用 为什么Link是从react-router-dom引入的 因为Link会被渲染为一个a标签,并且需要处理点击事件,和浏览器是相关联的 路由表和路由的书写技巧 不可以写斜杆,写斜杆代表不管你从哪里开始,我都是从/开始的, 所以我们需要在不破坏当前路由的基础上把路由加进去,所以不能有//带了斜杆就是根了(也就url的路径从我开始算起)./在不破坏当前路径下载后面添加内容,也可以不写./于是乎下面三个效果都是一样的,最终匹配的都是/home/message //路由表 {path:/home,element:Home/children:[path:./message,path:/home/message,path:message,] } //路由链接的to的书写也和这个相同, //下面二个均是在对应url后面添加相应的内容 //比如之前url为/home,那么点击message组件显示, //那么url就会变为/home/message NavLink tomessageMessage组件显示/NavLink NavLink tonewsNews组件显示/NavLink一个很奇怪的问题 必须要转化为字符串才可以 return Link to{String(project.id)}{ project.name } /Link注意Navigate写法 h1ProjectScreen/h1Link to{kanban}看板/LinkLink to{epic}任务组/LinkRoutesRoute path{kanban} element{KanbanScreen/}/RouteRoute path{epic} element{EpicScreen/}/Route{/*/!*Navigate to{window.location.pathname /kanban}/*!/ react5写法*/}Route path{} element{Navigate to{kanban}/}//Routes/divuseSearchParams初步完成 可以用来获取search参数(也就是url后面的这种参数/a?id4age14)返回一个URLSearchParams,所以要读取某一个值就需要使用get方法 //url/a?id4age14 const [a] useSearchParams(); a.get(age);//输出14问题 为什么没有as const会出现下图问题 import {useSearchParams} from react-router-dom; export const useUrlQueryParams (keys:string[]) {const [searchParams] useSearchParams();return [keys.reduce((pre,key) {return {...pre,[key]:searchParams.get(key) ?? }},{} ),searchParams,] }先来看一个小例子 想一想a的类型是什么 const a [jack,12,{gender:男}]原因很简单,因为ts认为数组都是相同的,使用为了确保数组当中都有相同的,所以就使用了多个|所以我们return后面添加一个as const即可 export const useUrlQueryParams (keys:string[]) {const [searchParams] useSearchParams();return [keys.reduce((pre,key) {return {...pre,[key]:searchParams.get(key) ?? }},{} ),searchParams,] as const } 不过这还不够,我们点入reduce的ts声明可以看到,reduce当中previousValue返回值依赖于初始化时候传入的泛型,所以我们可以指明initialValue在reduce当中 reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) T, initialValue: T): T;最终初步完成如下 export const useUrlQueryParams (keys:string[]) {const [searchParams,setSearchParams] useSearchParams();return [keys.reduce((pre,key) {return {...pre,[key]:searchParams.get(key) ?? }},{} as {[key in string]:string}),setSearchParams,] as const }useSearchParams完成(使用useMemo解决) 上一次完成的方法我们使用发现会无限渲染我们可以借助why-did-you-render来检测是什么造成了页面渲染 通过排查,useDebounce当中的useEffect发现依赖项目变化了,进而去重新渲染页面,但是params在每次渲染的时候都是一个新的,导致useEffect又认为发生了变化,进而重复无限渲染 所以我们使用useEffect的时候,我们应该将基本类型和组件状态(使用useState)放置到依赖里面,非组件状态的对象,绝对不可以放在依赖里面!!所以检查我们可以使用why-did-you-render const [params] useUrlQueryParams([name,age])const debounceValue useDebounce(params,100);/*自定义防抖hooks*/ export const useDebounce T(value:T,delay?:number):T {const [debounceValue,setDebounceValue] useState(value)useEffect(() {const timer setTimeout(() { setDebounceValue(value) },delay);return () {/*下一次effect执行前的处理*/clearTimeout(timer)}},[value,delay])return debounceValue; }所以我们循环遍历search参数的时候,返回的是一个对象,又因为react只对对象进行地址比较,所以就导致每次重新渲染返回的对象不同,所以就造成了重复渲染,解决后代码如下(使用useMemo) export const useUrlQueryParams K extends string(keys:K[]) {const [searchParams,setSearchParams] useSearchParams();return [useMemo(() {return keys.reduce((pre,key) {return {...pre,[key]:searchParams.get(key) ?? }},{} as {[key in K]:string})},[searchParams]),setSearchParams,] as const } 完成的URL状态管理代码 export const useUrlQueryParams K extends string(keys:K[]) {const [searchParams,setSearchParams] useSearchParams();return [useMemo(() {return keys.reduce((pre,key) {return {...pre,[key]:searchParams.get(key) ?? }},{} as {[key in K]:string})},[searchParams]),(params:Partial{[key in K] : unknown}) {const o cleanEmptyObj({...Object.fromEntries(searchParams),...params}) as URLSearchParamsInitreturn setSearchParams(o);}] as const } 实现Id-Select解决Id难题 获取AntDesign组件当中属性的二种方式 方法1:通过React.ComponentProps; import React from react; import {Select} from antd;type SelectProps React.ComponentPropstypeof Select;方法2:Ctrl按照进入组件,然后找到后复制粘贴就可以引入 import {SelectProps} from antd/es/select;用useEditProject编辑项目 柯里化 因为有一个参数实现已经知道了,后一个参数需要等待才可以知道,就可以采用这种方式 const { mutate } useEditProject(); const pinProject (id:number) (checked:boolean) mutate({id,pin:checked}) Pin checked{project.pin} onCheckedChange{pinProject(project.id)}/等同于 const pinProject (id:number,checked:boolean) {mutate({id,pin:checked}) } Pin checked{project.pin} onCheckedChange{(checked:boolean) pinProject(project.id,checked) }/惰性初始化和使用useRef保存函数和useState保存函数的方法 什么是惰性初始化 惰性初始化的时候(也就是传入一个函数就是惰性初始),此函数会被立即执行,并将返回的值作为返回数组的第一个参数,其他和普通state是相同的(调用setState也会触发页面重新渲染)惰性初始代码 示例:https://stackblitz.com/edit/react-ts-2meygh?fileApp.tsx const [state,setState] useState(() {const initialState someExpensiveComputation(props);return initialState; })非惰性初始(也就是普通的state,传入的不是一个函数,) const [state,setState] useState(someExpensiveComputation(props)); const [state,setState] useState({name:李白,sex:男 })所以当我们想通过useState保存函数的时候,就不可以了,我们可以使用useRef来保存函数 useRef 我觉得我快忘记了,再看看React官网的描述吧 比较重点就是ref对象发生变化,并不会引发组件的重新渲染,useRef的值是保存在一个.current属性当中 老师在最后说的一个问题,将代码改为下面样子,我们点击设置callBack后,再点击执行callBack-设置后不正常输出init按钮,发现输出的却是init 这是因为组件在编译渲染完成后,执行callBack-设置后不正常输出init按钮始终指向初次时候的函数地址,又因为useRef即使被更新也不会被重新渲染,导致此按钮指向的依旧是初始化时候的函数而为什么执行callBack-设置后正常输出updated按钮却是正常输出,因为传入的是高阶函数,在执行的时候才会寻找current所指向的函数去执行,所以执行就没有问题 import * as React from react import ./style.css;export default function App() {const callBackRef React.useRef(() {return alert(init);});const callBackCurrent callBackRef.current;console.log(输出查看callBack的current(是否重新渲染), callBackCurrent);// 设置callBackconst setCallBack () {callBackRef.current () {return alert(updated);};};//执行callBack - 正常const fnCallBack () {callBackRef.current();};return (divbutton onClick{setCallBack}设置callBack/buttonbutton onClick{fnCallBack}执行callBack-设置后正常输出updated/buttonbutton onClick{callBackRef.current}执行callBack-设置后不正常输出init/buttonh1Hello StackBlitz!/h1pStart editing to see some magic happen :)/p/div); }useState保存函数的方法 既然存在惰性初始化,我们就让他执行就可以,我们返回一个函数就可以啦 const [retry,setRetry] useState(() () {//返回一个函数 })优化异步请求 出现的情况 在外面等待数据返回的时候,突然退出登录,然后未中断请求,导致setData等操作会出现异常(我练习的时候好像没有,不过也学习下) 解决异步的时候退出的问题 //1.建立useMountedRef,并设置状态 /* * 返回组件的状态,如果没有挂载或者已经卸载,则返回false, * */ export const useMountedRef () {const mountedRef useRef(false);useEffect(() {mountedRef.current true;return () {mountedRef.current false;//组件卸载}})return mountedRef }// 伪代码,当调用then的时候判断是否组件卸载了 return promiseGive.then((res:D) {if(mountedRef.current){setData(res);}return Promise.resolve(res);//实现链式调用 }) .catch(error {setError(error);if(config.throwOnError) return Promise.reject(error);return error;//实现链式调用 })当使用useCallback,或者useMemo的时候,可以里面会有依赖state,然后我们会将依赖加入进去,但是加入后,会发生无限循环的问题,这个时候我们就可以使用setData的第二种形式了,然后结合useCallback或者useMemo //第一种(造成无限循环)//当data改变,触发重新渲染,因为当调用setData的时候,就代表data改变//然后触发useCallback的重新渲染,然后又调用setData,又代表data改变//又重新触发setData,,,,,,这样子无限循环下去 const handleChange useCallback(() {const [data,setData] useState({name:李白,loading:false})setData({...state,loading:true}) },[data,setData])//第二种(解决无限循环) const handleChange useCallback(() {const [data,setData] useState({name:李白,loading:false})setData((preState) {...preState,loading:true}) },[setData])什么时候使用useMemo,useCallback 非基本类型想做依赖,就需要使用这二个比如在想自定义hooks的时候,返回了函数,或者非基本数据类型,或者并没有被useState包裹的数据的时候,就需要使用这二个了 状态提升 const其实变量提升(依照var的变量提升,我们可以想一想组件的状态提升) 目前我们为了使用一个能被多个组件调用的方法或者是属性(可以称其为全局方法或属性),我们将其提升到共同的父组件当中,但是当子组件需要使用全局方法或属性的时候,父组件和要使用的子组件只有一层还好说,当有多层的时候,就会出现父传给A组件,A组件传递给B组件,B组件在传给C组件,C组件再来使用,来看看下面集中方法 第一种: 放在全局状态,通过一层一层传递, 第二种: 还有一种Context的方法 react官网说明 不适用content传递 class App extends React.Component {render() {return Toolbar themedark /;} }function Toolbar(props) {// Toolbar 组件接受一个额外的“theme”属性然后传递给 ThemedButton 组件。// 如果应用中每一个单独的按钮都需要知道 theme 的值这会是件很麻烦的事// 因为必须将这个值层层传递所有组件。return (divThemedButton theme{props.theme} //div); }class ThemedButton extends React.Component {render() {return Button theme{this.props.theme} /;} }使用createContext传递 // Context 可以让我们无须明确地传遍每一个组件就能将值深入传递进组件树。 // 为当前的 theme 创建一个 context“light”为默认值。 const ThemeContext React.createContext(light); class App extends React.Component {render() {// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。// 无论多深任何组件都能读取这个值。// 在这个例子中我们将 “dark” 作为当前的值传递下去。return (ThemeContext.Provider valuedarkToolbar //ThemeContext.Provider);} }// 中间的组件再也不必指明往下传递 theme 了。 function Toolbar() {return (divThemedButton //div); }class ThemedButton extends React.Component {// 指定 contextType 读取当前的 theme context。// React 会往上找到最近的 theme Provider然后使用它的值。// 在这个例子中当前的 theme 值为 “dark”。static contextType ThemeContext;render() {return Button theme{this.context} /;} }第三种: 组合组件(component composition) 其实就是将组件传递(传递jsx) - 缺点是没有解决向下钻的问题,但是方法不需要传递了,(定义和调用很近),示例如下 // 使用component composition function Page(props) {const user props.user;const userLink (Link href{user.permalink}Avatar user{user} size{props.avatarSize} //Link);return PageLayout userLink{userLink} /; }// 现在我们有这样的组件 Page user{user} avatarSize{avatarSize} / // ... 渲染出 ... PageLayout userLink{...} / // ... 渲染出 ... NavigationBar userLink{...} / // ... 渲染出 ... {props.userLink}//未使用component composition之前 Page user{user} avatarSize{avatarSize} / // ... 渲染出 ... PageLayout user{user} avatarSize{avatarSize} / // ... 渲染出 ... NavigationBar user{user} avatarSize{avatarSize} / // ... 渲染出 ... Link href{user.permalink}Avatar user{user} size{avatarSize} / /LinkuseUndo 未使用useReducer的写法 import {useCallback, useState} from react;const UseUndo T(initData:T) {const [state,setState] useState{backList:T[],//过去的记录present:T,//现在的值,goList:T[],//前面的记录}({backList:[],goList:[],present:initData,})const [canBack,setCanBack] useState(() state.backList.length 0);//是否可以后退const [canGo,setCanGo] useState(() state.goList.length 0);//是否可以前进/* 执行返回 */const execBack useCallback(() {setState((currentState) {if(!canBack) return currentState;const { goList:oldGoList,backList:oldBackList } currentState;const present oldBackList[oldBackList.length-1];const backList oldBackList.slice(0,oldGoList.length - 1);const goList [...oldGoList,present];return {goList,backList,present,}})},[])/* 执行前进 */const execGo useCallback(() {setState((currentState) {if(!canGo) return currentState;const { goList:oldGoList,backList:oldBackList } currentState;const present oldGoList[0];const goList oldGoList.slice(1);const backList [...oldBackList,present];return {goList,backList,present,}})},[])/* 设置值 */const set useCallback((newData:T) {setState((currentState) {const { goList:oldGoList,backList:oldBackList,present:oldPresent } currentState;if(newData oldPresent) return currentState;const backList [...oldBackList,oldPresent];return {goList:[],backList,present:newData,}})},[])/* 重置 */const reset useCallback(() {setState((currentState) {const { goList,backList } currentState;return {goList:[],backList:[],present:initData,}})},[])return [state,execBack,execGo,set,reset,canBack,canGo,] as const }export default UseUndo 使用useReducer的写法,仅供参考 import {useCallback, useReducer, useState} from react;export enum TypeOperation {gogo,backback,setset,resetreset, }export interface StateT {backList:T[],//过去的记录present:T,//现在的值,goList:T[],//前面的记录 }export interface ActionT {newPresent?:T,type: TypeOperation, } const unDoReducer T(state:StateT,action:ActionT) {const {type,newPresent} action;const { goList:oldGoList,backList:oldBackList,present:oldPresent } state;switch (type){case back: {if(oldBackList.length 0 ) return state;const present oldBackList[oldBackList.length-1];const backList oldBackList.slice(0,oldGoList.length - 1);const goList [...oldGoList,present];return {goList,backList,present,}}case go: {if(oldGoList.length 0) return state;const present oldGoList[0];const goList oldGoList.slice(1);const backList [...oldBackList,present];return {goList,backList,present,}}case reset:{return {goList:[],backList:[],present:newPresent,}}case set: {if(newPresent oldPresent) return state;const backList [...oldBackList,oldPresent];return {goList:[],backList,present:newPresent,}}default: return state;} }const UseUndo T(initData:T) {const [state,dispatch] useReducer(unDoReducer,{backList:[],goList:[],present:initData,})const [canBack,setCanBack] useState(() state.backList.length 0);//是否可以后退const [canGo,setCanGo] useState(() state.goList.length 0);//是否可以前进/* 执行返回 */const execBack useCallback(() dispatch({type:TypeOperation.back}),[dispatch])/* 执行前进 */const execGo useCallback(() dispatch({type:TypeOperation.go}),[dispatch])/* 设置值 */const set useCallback((newData:T) dispatch({type:TypeOperation.set,newPresent:newData}),[dispatch])/* 重置 */const reset useCallback((newData:T) dispatch({type:TypeOperation.reset,newPresent:newData}),[dispatch])return [state,execBack,execGo,set,reset,canBack,canGo,] as const }export default UseUndo 顺带一提,其实useReudcer里面的action,其实什么都可以,只是我们用的多的都是带有type的而已 redux redux用在哪里都可以,react-redux连接redux.如果redux不使用在react,就可以不适用react-redux redux作用就是用现在的state,产生下一个state 当redux发生什么事情的时候,就会戳一下 redux发生dispatch,触发subscribedispatch - counter - store redux保持一个同步之前学习的,为什么呢?保持纯洁性,因为如果是异步请求了,就不是可预测了的 副作用,对现实世界产生影响 比如发送请求,修改全局变量 redux怎么知道要更新数据呢?怎么知道要执行订阅的内容呢? 判断前一次的数据是否和后一次的数据相同,相同就不更新,不相同就不更新这样子比较 变量 a 变量b redux-thunk在redux里面处理异步流行的一个库(注意,redux可以进行异步操作,但是redux-thunk可以帮助我们隐藏异步实现的细节) 可以看到,组件内部并不想知道怎么请求的,(具体异步细节忽略) reduxjs/tooltik和react-redux 安装依赖 yarn add react-redux reduxjs/tooltik有关reduxjs/toolkit部分 书写片段 project-list.slice.ts import {createSlice} from reduxjs/toolkit;export const projectListSlice createSlice({name:projectListSlice,initialState:{projectModalOpen:false,},reducers:{/* 开启对话框 */openProjectModal(state,action){//immer帮我们处理了,所以我们可以直接在返回的state书写state.projectModalOpen true;},/* 关闭对话框 */closeProjectModal(state,action){state.projectModalOpen false;}} })export const projectListSliceReducer projectListSlice.reducer; 主入口 index.tsx import {configureStore} from reduxjs/toolkit; import {projectListSliceReducer} from ../pages/projectList/projectList.slice;export const store configureStore({/* 设置状态管理 */reducer:{projectList:projectListSliceReducer}, }) 有关react-redux import {AuthProvider} from ./authContext; import React, {ReactNode} from react import {store} from ../store;//新增 import {Provider} from react-redux;//新增 export const AppProvider ({children}:{children:ReactNode}) {return (Provider store{store}AuthProvider{ children }/AuthProvider/Provider) } export default AppProvider; 使用 获取设置的state参数const { useSelector } from react-redux import {useSelector} from react-redux const selectProjectModalOpen state state.projectList.projectModalOpen; const showModal useSelector(selectProjectModalOpen);//等同于 const showModal useSelector((state) state.projectList.projectModalOpen)调用设置的方法const { useDispatch } from react-redux const { useDispatch } from react-redux; import {projectListSliceActions} from ../projectList/projectList.slice; const dispatch useDispatch();//不需要传入任何参数,react-redux会自动去处理store button onClick{() dispatch(projectListSliceActions.closeProjectModal())}点击我关闭/button这里面的projectListSliceActions对应下面暴露出来的projectListSliceActions import {createSlice} from reduxjs/toolkit;export const projectListSlice createSlice({name:projectListSlice,initialState:{projectModalOpen:false,},reducers:{/* 开启对话框 */openProjectModal(state){//immer帮我们处理了,所以我们可以直接在返回的state书写state.projectModalOpen true;},/* 关闭对话框 */closeProjectModal(state){state.projectModalOpen false;}} })export const projectListSliceReducer projectListSlice.reducer; export const projectListSliceActions projectListSlice.actions; //获取state,从store当中的reducer获取值 export const selectProjectModalOpen (state) state.projectList.projectModalOpen;执行异步 具体可看这篇文章,这里做个记录~ https://blog.csdn.net/m0_71485750/article/details/126764667 方法1: 在home.js中, 通过createAsyncThunk函数创建一个异步的action 再在extraReducers中监听这个异步的action的状态, 当他处于fulfilled状态时, 获取到网络请求的数据, 并修改原来state中的数据 import { createSlice, createAsyncThunk } from reduxjs/toolkit import axios from axios// 创建一个异步的action export const fetchHomeMultidataAction createAsyncThunk(fetch/homemultidata, async () {const res await axios.get(http://123.207.32.32:8000/home/multidata)// 返回结果会传递到监听函数的actions中return res.data })const homeSlice createSlice({name: home,initialState: {banners: [],recommends: []},// extraReducers中针对异步action, 监听它的状态extraReducers: {[fetchHomeMultidataAction.fulfilled](state, { payload }) {// 在fulfilled状态下, 将state中的banners和recommends修改为网络请求后的数据state.banners payload.data.banner.liststate.recommends payload.data.recommend.list}} })export default homeSlice.reducer 其他地方引入执行异步 import { useDispatch } from react-redux; const dispatch useDispatch(); dispatch(fetchHomeMultidataAction())方法2 如果我们不想通过在extraReducers在监听状态, 再修改state这种方法的话, 还有另外的一种做法我们创建的fetchHomeMultidataAction这个异步action是接受两个参数的参数一, extraInfo: 在派发这个异步action时, 如果有传递参数, 会放在extraInfo里面 参数二, store: 第二个参数将store传递过来 这样我们获取到结果后, 通过dispatch修改store中的state, 无需再监听异步action的状态import { createSlice, createAsyncThunk } from reduxjs/toolkit import axios from axios// 创建一个异步的action export const fetchHomeMultidataAction createAsyncThunk(fetch/homemultidata, // 有传递过来两个个参数, 从store里面解构拿到dispatchasync (extraInfo, { dispatch }) {// 1.发送网络请求获取数据const res await axios.get(http://123.207.32.32:8000/home/multidata)// 2.从网络请求结果中取出数据const banners res.data.data.banner.listconst recommends res.data.data.recommend.list// 3.执行dispatch, 派发actiondispatch(changeBanners(banners))dispatch(changeRecommends(recommends))} )const homeSlice createSlice({name: home,initialState: {banners: [],recommends: []},reducers: {changeBanners(state, { payload }) {state.banners payload},changeRecommends(state, { payload }) {state.recommends payload}} })export const { changeBanners, changeRecommends } homeSlice.actionsexport default homeSlice.reducer 其他地方引入执行异步 import { useDispatch } from react-redux; const dispatch useDispatch(); dispatch(fetchHomeMultidataAction())总结 先看看片段 projectList.slice.ts import {createSlice} from reduxjs/toolkit;export const projectListSlice createSlice({name:projectListSlice,initialState:{projectModalOpen:false,},reducers:{/* 开启对话框 */openProjectModal(state){//immer帮我们处理了,所以我们可以直接在返回的state书写state.projectModalOpen true;},/* 关闭对话框 */closeProjectModal(state){state.projectModalOpen false;}} })export const projectListSliceReducer projectListSlice.reducer; export const projectListSliceActions projectListSlice.actions; //获取state,从store当中的reducer获取值 export const selectProjectModalOpen (state) state.projectList.projectModalOpen;store/index.ts import {configureStore} from reduxjs/toolkit; import {projectListSliceReducer} from ../pages/projectList/projectList.slice;export const store configureStore({/* 设置状态管理 */reducer:{projectList:projectListSliceReducer}, }) 获取数据使用useSelector (import {useSelector} from react-redux) import { useSelector } from react-redux const selectProjectModalOpen state state.projectList.projectModalOpen; const showModal useSelector(selectProjectModalOpen);//等同于 const showModal useSelector((state) state.projectList.projectModalOpen)触发方法使用useDispatch ( import { useDispatch } from react-redux ) import { useDispatch } from react-reduximport {projectListSliceActions} ./projectList.slice;button onClick{() dispatch(projectListSliceActions.closeProjectModal())}点击我关闭/button 用代码分割优化性能 react官网说明 https://zh-hans.reactjs.org/docs/code-splitting.html 使用React.lazy 需要配合Suspense组件使用,传入fallback参数作为加载时候的画面注意组件要使用默认导出 import React,{lazy,Suspense} from react; import {useAuth} from ./context/authContext; import ./App.css; import {Boundary} from ./component/errorBoundary; import {FullErrorFallBack, FullPageLoading} from ./component/lib; //import UnAuthenticated from ./pages/unAuthenticated; //import Authenticated from ./pages/authenticated;const UnAuthenticated lazy(() import(./pages/unAuthenticated)) const Authenticated lazy(() import(./pages/authenticated))function App() {const {userInfo} useAuth();/*测试错误*/return (div classNameAppBoundary fallBackRender{FullErrorFallBack}Suspense fallback{FullPageLoading}{userInfo Object.keys(userInfo).length ? Authenticated/ : UnAuthenticated/}/Suspense/Boundary/div); }export default App; 使用React.memo 使用后,只有当组件的props或者全局状态,比如redux发生变化的时候,才会执行重新渲染 Profiler 生产环境禁止使用 如果需要,在编译的时候添加(如果是create react app)创建的话 yarn build --profile npm run build --profile单元测试(React) 我们setupWorker以前曾为开发创建了一个假服务器。现在我们使用不同的函数setupServer因为测试将在 Node.js 环境中运行而不是在实际的浏览器环境中。只有浏览器环境具有 Service Worker 功能因此我们在测试中使用 MSW 的方式实际上并不涉及 Service Worker。 也就是说setupServer在node环境下使用的,不涉及到浏览器,setupWorker在浏览器中运行的 最基础的单元测试 随便哪里建立一个sum函数 export function sum(a, b) {return a b; }src/tests/sun.ts文件(其实你取名叫sun.test.ts也可以,其实都会识别) import {sum} from ../utils;test(测试结果是否为100,() {expect(sum(50,50)).toBe(100) })然后运行yarn test 可以看到自动去寻找了__tests__当中的文件进行测试 先看看msw拦截异步请求的代码怎么写 总的来说就是: 创建handler,使用handler,开始拦截 1.创建handler import {rest} from msw;export const handlers [// 用于登录rest.post(/login,(req,res,context) {sessionStorage.setItem(is-authenticated,true);//设置登录状态为真return res(context.status(200))}),// 用于获取用户信息rest.get(/user,(req, res, context) {const isAuthenticated sessionStorage.getItem(is-authenticated);//未认证的用户if(!isAuthenticated){return res(context.status(403),context.json({errorMessage:未进行认证}))}// 已经认证的用户return res(context.status(200),context.json({username:admin,address:dreamlove.top}))}), ] 2.使用handler import { setupWorker,SetupWorker } from msw; import { handlers } from ./handler;export const worker:SetupWorker setupWorker(...handlers) 3.开始拦截(只要匹配到了handler当中的列表,就进行拦截) 我访问/login或者/list就会被拦截,从而返回假数据 import { worker } from ./mocks/browserif(import.meta.env.DEV){/* 开发环境下就启动 */worker.start(); }演示地址 github:https://github.com/superBiuBiuMan/mswSimpleStudycodesandbox:https://githubbox.com/superBiuBiuMan/mswSimpleStudy setupServer和setupWorker 也就是说setupServer在node环境下使用的,不涉及到浏览器,setupWorker在浏览器中运行的老师的代码没有过多解释,我们就以下面代码做个简单说明 其实老师大概步骤也和上面msw拦截异步请求代码一样先监听,然后拦截,之后才是创建 import {setupServer} from msw/node; import { rest } from msw;//用于发送假数据const server setupServer();//文件内所有测试开始前执行的钩子函数 beforeAll(() {server.listen(); })//文件内每个测试完成后执行的钩子函数(执行完test之后执行的) afterEach(() {server.resetHandlers(); })//文件内所有测试完成后执行的钩子函数 afterEach(() {server.close(); })test(发送异步请求,async () {/* 其实这一步就是向handler添加数据 */server.use(rest.get(/list,(req,res,ctx) {return res(ctx.status(200),ctx.json([{name:李白1,age:18},{name:李白2,age:19},{name:李白3,age:20},]))}));const response await fetch(/list)const result await response.json();expect(result).toEqual([{name:李白1,age:18},{name:李白2,age:19},{name:李白3,age:20},]) }) 其实很简单,server.use理解为向监视器里面添加东西,添加的东西会被拦截并做处理,只不过我们每次测试完毕,都将里面的handler进行了清空 额外小知识 encodeURIComponent(转义URL当中部分字符的) encodeURI(转移整个URL内容的) .env,.env.development 当为npm start的时候.webpack会去读取.env.development文件当为npm run build编译之后,webpack会去读取.env.development文件并将读取的文件作为一个对象存储在process.env当中比如.env.development有如下内容 abc www.baidu.com那么我们就可以读取 process.env.abc如果是vite读取,则需要通过如下来读取,并且设置的变量必须要以VITE_开头 const apiUrl import.meta.env遇到这种不明确的情况(比如name),到底是搜索名字为空的,还是要忽略这种情况呢?所以我们需要在前端做处理 TS7016: Could not find a declaration file for module ./screens/projectList. D:/develop/phpstudy_pro/WWW/React-cli/react_17_projectJira/src/screens/projectList/index.jsx implicitly has an any type.这种报错 就是缺少声明文件,要么自己添加对应的xxx.d.ts文件或者使用//ts-ignore进行忽略 useHooks(不管是自带的hooks还是自己创建的hooks),不可以在普通函数中运行,只能在hooks当中使用或者其他hooks使用,所以在自定义hooks的时候,需要以useXxx开头
http://www.hkea.cn/news/14310251/

相关文章:

  • 做动态图片的网站吗专业手机网站建设
  • 做网站时的注册权起到什么作用WordPress与dz用户恭喜
  • 烂网站做竞价行吗宁波城乡建设网站
  • 网站开发系统架构图wordpress点播视频模版
  • 淘客怎么建网站做推广厦门seo优
  • 电视台网站建设方案.docwordpress 热门排行
  • 手机网站策划书2345网址导航下载到桌面
  • 淘宝网站建设h5和手机网站
  • 尖扎网站建设公司装修设计的网站
  • 山东省建设厅网站一体化平台wp网站建设模板
  • 大型购物网站设计移动端显卡
  • 织梦网站地图怎么做xml网页翻译扩展
  • 网站怎么实现两种语言花都网站推广
  • 公司网站建设计划书专业制作网站用哪些软件
  • 当当网站建设优点网站左侧图片悬浮代码
  • 苏州高端网页设计宁波seo整体优化公司
  • 外贸自建站平台怎么选旅游网站的设计与建设
  • 宜春网站建设前端直播网站怎么做
  • 英文模板网站中国城乡住房和城乡建设部网站首页
  • 做消防哪些网站找工作门户网站建设软件
  • 网站备案中打不开网页空间网站
  • 怎么制做网站网站app开发计划书
  • 个人备案的域名拿来做别的网站网站建设职业怎么样
  • 泰安市建设信息网站销售管理系统模板
  • 建设人行官方网站重庆做网站设计
  • 美食网站建设项目预算o2o网站建设效果
  • 淮安高端网站制作济南行知网站制作
  • 中国域名网站网络营销与管理专业
  • 网站怎么做移动图片不显示深圳勘察设计协会
  • 仿制型模板网站做简图的网站