# outline **Repository Path**: gzh52202/outline ## Basic Information - **Project Name**: outline - **Description**: 四阶段课程内容 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 4 - **Created**: 2022-09-19 - **Last Updated**: 2022-11-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 四阶段课程 ## 自我介绍 ### 基本信息 * 名字 * 年龄 * 学校 * 专业 * 工作年限 * 性格 ### 能力 * 知识:会什么 * 经验:做过什么 * 能力:学习能力与沟通能力 * 业绩:有什么成果 ## day1-1 ### 面试题 ### 知识点: React * 使用react > 需要引入两个js文件,分别为react.js + react-dom.js ```js const target = document.querySelector('#app') const vNode = React.createElement('div') // v18+ const root = ReactDOM.createRoot(target) root.render(vNode) // v17- ReactDOM.render(vNode,target) ``` * 版本号: 大版本.小版本.补丁 > 18.0.0 -> 18.0.1 * 节点 * 真实节点(RealDOM): 在浏览器中渲染的节点 > 创建真实节点:`document.createElement(type)` * 虚拟节点(VirtualDOM):一个结构类似于真实节点的js对象 > 可以让用户不直接操作真实节点,进而提升页面性能 * 创建虚拟节点:`React.createElement(type)` * JSX > 是`React.createElement()`的语法糖 * JSX语法规范 * 在元素属性不能直接使用js关键字 * class -> className * for -> htmlFor * 多个单词的属性必须使用驼峰 * autofocus -> autoFocus * onkeyup -> onKeyUp * 必须结束标签 * * * 插值:{} > 差值可以用在标签内或标签属性中 * 注释 ```js const msg = 'success'
{msg}
``` * 从0配置基于webpack的react项目环境 * 创建目录与文件 * 安装依赖 * webpack + webpack-cli + webpack-dev-server * react + react-dom * @babel/core + @babel/preset-react + babel-loader * 配置webpack.config.js ```js { module:{ rules:[ // 编译JSX:babel { test:/\.js$/, use:{ loader:'babel-loader', // 配置babel插件 options:{ // plugins:[] presets:['@babel/preset-react'],// 插件集合 } } } ] }, plugins:[ new HtmlWebpackPlugin({ template:'./src/template.html' }) ] } ``` * npm script * 组件 * 模块化优势 * 命名冲突 * 复用 * 分工 * 维护 * 定义组件 > 定义一个组件就是创建一个元素 * 分类 * 函数组件(推荐) > 必须有返回值 * 类组件 > 继承自React.Component,必须包含一个render方法,且render方法必须有返回值 * 规则 * 组件名必须大写字母开头 * 只能包含一个顶层元素 * 组件刷新 * 状态(一种能监听变化的数据):React会监听状态的变化,当状态发生变化时,自动刷新组件(重新执行render函数) * 获取状态:`this.state.xxx` * 修改状态:`this.setState()` * 组件的数据挂载方式 * 普通数据挂载:插值 ```js

{title}

``` * state状态 > 修改状态:`this.setState()` ```js
{this.state.count}
``` * 条件渲染: 三元运算 ```js { idx===current ? 'block' : 'none'} { show ?
: } ``` * 列表渲染 > key必须为唯一且稳定的值 * map() * filter() * ... * 事件处理 * 使用驼峰 ### 练习 ## day1-2 ### 面试题 * 数据类型 * 基本数据类型 * String * Number * Boolean * true * false * Null * null * Undefined * undefined * Symbol * BigInt * 引用数据类型 * Object ### 知识点 * todolist待办事项 * 增 ok * 删 ok * 改 ok * 查 ok * 思维转变 > 节点操作思维 -> 数据驱动思维 * 事件 * this指向 > 默认情况下,只有在 `render`、`contructor`(调用super后)、`生命周期函数`中的this指向组件实例 * 改变this指向 * `fn.call(target,...args)` / `fn.apply(target,[...args])`: 执行fn并改变fn的this指向 * `fn.bind(target,...args)`:返回一个与fn一样的函数,并把它的this指向改成target * target: this指向目标 * 箭头函数 * event对象 > 事件处理函数的最后一个参数 * 传参 * `bind(this,xxx)` * 立即执行函数并返回一个函数 * 受控组件: > 表单内容受到组件state的控制,表单与state绑定后,必须同时提供修改state的事件(onChange),否则无法修改表单内容 * 非受控组件:通过原生节点的方式制作表单 * ref引用 ```js this.input=el} /> ``` * xss攻击:跨域脚本攻击 > 过滤有破坏性的代码(过滤script、style、iframe等标签) * 组件化开发Todolist * 拆分组件 * 复用 * 分工 * 维护 * 编写组件 * TodoHead * TodoContent * TodoFoot * TodoItem * 数据定义 * 状态提升: 如果多个组件使用同一个数据,则定义到他们共同的父级 * 组件通讯 * 父->子:props > 子组件无法修改父组件传入的数据,如要修改数据(状态),需要遵循谁的数据谁修改 1. 传递:给子组件添加属性并传递父组件数据 2. 接收: * 类组件: * this.props * constructor的第一个参数 * 子->父: * 把父组件函数传到子组件中执行,并回传参数 ### 练习 * 完成组件化todolist案例 ## day1-3 ### 面试题 * var, let, const ```js // 块级作用域 var i=10 //for(var i=0;i<5;i++){ //} // console.log(i); //5 for(let i=0;i<5;i++){ } console.log(i); // 10 // var声明提前 console.log(b);// undefined console.log(a); // a is not defined let a=10; var b=20; // let,const在同作用域下不能多次声明 ``` * 事件传播过程 * 捕获阶段 * 冒泡阶段 ```js div.onclick = function(){ console.log(666) } div.addEventListener('click',function(){ console.log(888) },true) span.onclick = function(){ console.log(999) } span.addEventListener('click',function(){ console.log(000) },true) ``` * 为什么存在跨域,如何解决跨域 > 安全问题 -> 同源策略 * 解决方案 * CORS(Cross Origin Resource Sharing)跨域资源共享 * 设置响应头 * Access-Control-Allow-Origin * Access-Control-Allow-Methods * Access-Control-Allow-Headers * 代理 * jsonp * iframe * 什么场景下出现隐式转换 > 当运算无法进行下去时,js会自动进行隐式转换,会自动调用valueOf()与toString()方法 ```js 10 - '5';// 5 '10'*2; // 20 var a = 10 if(a){ } var b = '10' if(b == 10){ } // let user = {username:'laoxie'} localStorage.setItem('user',user); // [object,Object] let user = { username:'laoxie', valueOf(){ return JSON.stringify(this) }, toString(){ return JSON.stringify(this) } } localStorage.setItem('user',user); // [object,Object] // 定义a变量,让以下条件成立 let a = { value:1, valueOf:function(){console.log('valueOf') return this.value++ }, toString:function(){console.log('toString') return this.value++ } } if(a==1&&a==2&&a==3){ console.log('success') } ``` ### 知识点 * 多层级组件通讯 * props逐层传递 * 操作繁琐 * 难以维护 * context 组件共享 1. 创建context ```js const context = React.createContext() ``` 2. 父组件共享数据:Provider ```js // 子组件 ``` 3. 子组件接收 * 类组件 * Consumer ```js { function(value){ return (
) } }
``` * contextType静态属性 ```js SubComponent.contextType = context; // 然后在组件中就可以通过this.context访问共享的数据 ``` * 函数组件 * Consumer * Hooks钩子函数:`useContext(context)` ```js import {useContext} from 'react' function TodoItem(){ const value = useContext(context) } ``` * 组件通讯 > 由于每个组件是相互独立的,组合起来后需要进行数据通讯 * 父->子:props > 不能在子组件修改父组件的数据 * 子->父: 把父组件方法传到子组件中执行,并回传数据 * 父->孙 * props逐层传递 * context * 兄弟:状态提升(把需要共享的数据放到他们共同的父级) * Todolist使用技术总结 1. React + Bootstrap + JSX 2. 组件通讯 * 使用props把父组件数据传递到子组件 * 使用context共享修改数据的方法 3. 类组件与函数组件 * 使用state状态实现组件的刷新 * 组件的划分 * 复用 * 分工 * 维护 4. 受控组件与非受控组件 5. 数据挂载方式 * 组件封装 > 为了复用而封装组件 * Button按钮组件 * 使用技术 * props ```js const size = 16 ``` * children > props.children ```js ``` * Render Props > 使用一个值为函数的 prop 共享代码的简单技术(父组件获取子组件数据) ```js ``` * props类型校验 > 给props设置类型检查器,当传入的props类型与规定的类型不匹配时,就会报错 * 给组件添加`propTypes`静态属性 ```js import PropTypes from 'prop-types' MyComponent.propTypes = { } ``` * props默认值 * 结构默认值 ```js const {type='Button'} = this.props ``` * defaultProps静态属性 ```js MyComponent.defaultProps = { type:'Button' } ``` * 使用模块 * classnames * 父组件获取子组件数据 * Render Props ```js // TodoItem.js // Button.js class Button extends React.Component{ state = { type:'Button' } render(){ return } } ``` * ref > ref用在组件上,得到组件实例的引用 ```js const refObj = React.createRef() // TodoItem.js this.btn.state.type ``` ## day1-4 ### 面试题 * 前端向后端发送数据的方式 * url参数: url params ``` /api/list?username=laoxie&password=123 ``` * express中如何获取 ```js router.get('/api/list',function(req,res){ const {username,password} = req.query; }) // api/goods/123 router.get('/api/goods/:id',function(req,res){ const {id} = req.params }) ``` * 请求体: request body ```js username=laoxie&password=123 ``` * express中如何获取 ```js router.use(express.urlencoded(),express.json()) router.post('/login',(req,res)=>{ const {username,password} = req.body }) ``` * 请求头: request Header * User-Agent * Cookie * Content-Type * express中如何获取 ```js app.use('/',(req,res)=>{ // 根据请求头数据相应不同的内容 const userAgent = req.get('User-Agent') // 百度做法 if(/windows/i.test(userAgent)){ res.send('PC端内容') }else{ res.send('移动端内容') } // 淘宝做法 if(/windows/i.test(userAgent)){ res.send('PC端内容') }else{ res.status(302) res.set({ location:'https://main.m.taobao.com/?sprefer=sypc00' }) } }) ``` * get请求与post请求的区别 * http与https的区别 * http: 明文传输,80 * https: 密文传输,443 * s: ssl ### 知识点 * 组件生命周期 > 组件从创建到销毁的过程 * 创建类组件 ```js // 类写法 class Button extends React.Component{ constructor(props){ super() this.state = { qty:10 } } render(){ return
} } // 老式写法 React.createClass({ getInitState(){ return {} } render(){ } }) ``` * 整个可以分成以下几个阶段,并执行相应钩子函数(生命周期函数) > 钩子函生命周期数:在生命周期执行过程中相应的时间执行的函数,开发者可以在这些函数中实现一些功能 * 初始化阶段(Initial) > 初始化state,props,context等 * constructor * 挂载阶段(Mounting) > 生成虚拟节点(oldDOM) -> 真实节点 -> 页面 * componentWillMount() > 不推荐 * render * componentDidMount() * 更新阶段(Upating) > state改变 -> 生成新的虚拟节点(newDOM) -> diff(差异算法) -> 差异项 -> 真实节点 -> 渲染到页面 * componentWillUpdate() > 不推荐 * render * componentDidUpdate() * diff算法对比规则 * 只进行同级对比 * 当前节点对比时如有变化,则直接替换,不进行下层对比 ```js oldDOM = { key:1, type:'div', children:[ {type:'span',children:10}, {type:'strong',children:20}, ] } newDOM = { key:1, type:'span', children:[ {type:'span',children:10}, {type:'strong',children:30}, ] } ``` * 销毁阶段:Unmounting * componentWillUnmount * 特殊钩子函数 * shouldComponentUpdate * componentWillReceiveProps > 不推荐 * 组件刷新场景 > 所谓的刷新,就是执行组件的render函数 * state改变 * props改变 * 父组件刷新导致子组件刷新 > 需要优化这样的场景 * 对比差异 * JSON.stringify() * lodash等工具 * 继承自PureComponent * 如何取消ajax * 设置超时时间(超时后自动取消) * 通过xhr.abort()手动取消 ```js let xhr = new XMLHttpRequest() xhr.timeout = 5000; xhr.open() xhr.send() ``` * Hooks > React16.8推出的新特性,用于增强函数组件的功能 * 组件刷新 * 类组件:组件刷新就是重新执行render函数 * 函数组件:组件刷新就是从头到尾把函数中的代码执行一遍 * 函数组件以前的缺点 * 无法自我刷新,只能依靠props的改变实现刷新 * 内置hooks函数 * `useState(default)`: 让函数组件也能实现 class 组件一样的状态特性(通过修改状态刷新组件) > useState执行后返回一个数组:`[state,setState]` * `useEffect(callback)`:callback在每轮渲染结束后执行,能实现类似于类组件中生命周期函数的效果 ### 练习 * 使用hooks实现todolist案例 ## day1-5 ### 面试题 1. 请求类型 > 语义化 * post * delete * put/patch(完全修改/部分修改) * get * options 预检请求(浏览器自动发起的请求,一般出现在复杂跨域) 2. 请求状态码 * 200+: 成功 * 300+: * 301 永久重定向 * 302 临时重定向 * 304 缓存 * 400+ 客户端的原因 * 401 无权限 * 402 需要支付才允许访问 * 403 禁止 * 404 不存在 * 500+ 服务端的原因 3. 函数组件与类组件的区别 * 类组件有state,生命周期,this等特性,函数组件没有 * 类组件有实例化过程,函数组件没有 * 函数组件的性能比类组件好 ### 知识点 * Hooks * 内置 * `useState(defaultValue)` > 实现与类组件状态类似的功能,返回一个数组 ```js // class component state = { qty:10, datalist:[] } this.setState({ qty:20 }) this.setState({ datalist:[10,20,30] }) // FC function Todolist(){ const [qty,setQty] = useState(10) const [datalist,setDatalist] = useState([]) setState(qty) } ``` * `useEffect(callback)` > 实现与类组件生命周期函数类似的功能,callback在页面渲染完成后执行 ```js useEffect(function(){}) useEffect(function(){},[]) useEffect(function(){},[qty]) useEffect(function(){ return function(){ } },[]) ``` * `useLayoutEffect(callback)` > useEffect的同步版本,里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成(**阻塞**了浏览器的绘制)一般用于节点操作 * `useMemo(callback)`: 缓存callback的返回值 > 一般用于编写一些比较耗费资源且无需重复执行的代码,以达到优化性能的目的,返回值为回调函数的结果 * `useCallback(callback)`:缓存callback本身 > 与 useMemo 类似,useCall缓存的是函数,一般用于定义事件处理函数 * `useRef(defaultValue)` > 与`createRef()`对比:有默认值且有缓存特性 ```js const [page,setPage] = useState(1) const ref = useRef() const handle = useCallback(()=>{ console.log(page,ref.current) },[]) ``` * `useContext(content)` > 比Consumer更简单的获取context共享数据的方式 ## day2-1 ### 面试题 * 本地存储存满了如何处理 * 本地存储大小 * cookie 4k * sessionStorage 5M * localStorage 5M * 解决方案 * 其他本地存储 * indexDB * webSQL * 利用不同的域实现扩容,并使用postMessage方案实现跨域通讯 * laoxie.quxuetrip.com * jingjing.quxuetrip.com ```js // laoxie.quxuetrip.com window.addEventListener('message',function(e){ console.log('data',e.data) }) // jingjing.quxuetrip.com laoxiewindow.postMessage('hello','http://laoxie.quexuetrip.com') ``` * webStorage如何实现cookie的有效期效果 * 临时cookie 使用 sessionStorage代理 * 具有有效期的cookie ? ```js localStorage.setItem('username','laoxie') localStorage.setItem('username',JSON.stringify({value:'laoxie',expires:'2022/9/30 09:05:00'})) localStorage.getItem('username');// laoxie, null // 重写locaStorage的原型方法 let now = new Date() //Date.parse(now)+3*24*60*60*1000 now.setDate(now.getDate()+3) localStorage.setItem('username','laoxie',now) const originSetItem = Storage.prototype.setItem Storage.prototype.setItem = function(key,value,expires){ if(expires){ value = JSON.stringify({ value, expires: Date.parse(expires) }) } // 必须改变this指向 originSetItem.call(this,key,value) } const originGetItem = Storage.prototype.getItem Storage.prototype.getItem = function(key){ let data = originGetItem.call(this,key); // null,'hello', '{value,expires}' try{ data = JSON.parse(data) if(data && data.expires){ // 判断是否过有效期 if(data.expires <= Date.now()){ data = null; this.removeItem(key) }else{ data = data.value } } }catch(err){ } return data } localStorage.getItem('username');// null,hello localStorage.getItem('password');// hello, null ``` ### 知识点 * 使用hook重写todolist * useImperativeHandle > 父组件获取子组件数据与方法,配合`forwardRef()`解决函数组件无法使用ref的问题 * 使用ref获取组件实例,然后通过组件实例得到子组件的数据与方法(前提是Child是类组件) ```js // Parent(ClassComponent) this.child=el}/> // Parent(FunctionComponent) const ref = useRef() useEffect(()=>{ ref.current },[]) return //Child Class Child extends React.Component{ } ``` * 子组件为函数组件 1. 使用forwardRef包裹函数组件,实现ref对象传递(函数组件的第二个参数) 2. 使用useImperativeHandle定义给ref暴露的属性 ```js function Parent(){ const ref = useRef() // 在这里获取子组件的数据 return ( ) } const Child = forwardRef(function(props,ref){ const [page,setPage] = useState(1) useImperativeHandle(ref,()=>{ return { a:10, page } }) return
}) ``` * useReducer > useState的增强版,一般用于复杂数据处理 ```js const [qty,setQty] = useState(1) const [goods,setGoods] = useState({ id:1, name:'goods1', price:998 }) // 修改价格 setGoods({ ...goods, price:1998 }) ``` * reducer 修改状态的方法,是一个**纯函数** > 纯函数:不修改传入的参数,固定输入得到固定输出 reducer不能修改传入的state,且必须返回一个新的state * 参数 * state * action > 格式:{type} * state 状态 ```js const [state,dispatch] = useReducer(reducer,initState) dispatch(action) ``` * try...catch ```js // let a a.test; // 这行代码报错,会停止执行后面所有的js代码 console.log('hello') try{ // 尝试执行这里的代码, // 如无报错,则执行执行后面的代码, // 如有错误,则进入catch并执行后面的代码 a.test }catch(err){ console.log('err',err) } console.log('hello') ``` * 自定义hook > 自定义一个以use开头的函数 * hook函数能在函数组件和自定义hook中使用 ```js function useStorage(){ } ``` ## day2-2 ### 知识点: react-router * nodejs包管理工具 * npm: node package managment * npm install > npm i * npm uninstall > npm uni * npm init * npm run * npm login > 登录npm服务器,一般用户发布npm包(注意镜像地址,类似于淘宝镜像是无法发布npm包,只能下载) * 镜像地址 * https://registry.npm.taobao.org/ * https://registry.npmjs.org/ * 修改镜像 ```bash npm config set registry=https://registry.npmjs.org/ ``` * yarn * 安装 ```bash npm install -g yarn ``` * 使用 * yarn add * yarn global add * yarn xxx * cnpm * pnpm * ... * 发布npm包 1. 注册账号 2. 登录npm服务器 > npm login,需要输入用户名和密码,邮箱与验证码(验证码发送到邮箱地址) 3. 创建自己的包 * package.json * 入口文件 * readme.md文档 4. 发布npm模块 > npm publish * react-router * 页面类型 * 单页面应用SPA(Single Page Application) > 整个应用只有一个页面(index.html),利用路由实现多视图效果 * 多页面应用MPA(Multiple Page Application) > 一个应用下有多个html页面,页面间通过a标签进行跳转 * 路由类型 > 必须指定路由类型才能使用路由配置 * hash路由:`HashRouter` > 浏览器地址有一个`#` * history路由: `BrowserRouter` > 没有#号,刷新会404 * 路由配置 * `Route` > 必须使用Routes组件包裹 * path * element ```js } /> // v5.0- ``` * `Routes` * 重定向:`` ```js // / -> /home } ``` * 路由跳转 * 组件跳转 * `Link` * `NavLink` > 与Link的区别是有高亮效果(配合css实现),默认当前类名`active` * to * replace 替换当前记录 ```js isActive?'current':null}/> ``` * 利用js跳转 * 函数组件:`useNavigate()` ```js const navigate = useNavigate() navigate('/home') navigate('/mine',{replace:true}) ``` * 类组件 ```js // v5- class Home extends React.Component{ render(){ return
} } class App extends React.Compponent{ render(){ this.props.history;// undefined } } App = withRouter(App) // v6: 取消了props中的history,location,match,且移除了withRouter高阶组件 } /> class Login extends React.Component{ render(){ // this.props.history;// undefined return
} } // 封装一个高阶组件:一个包装函数 // 需求:在类组件中实现js路由跳转 function withNavigate(InnerComponent){ return function Wrap(){ const navigate = useNavigate() return } } ``` * React的UI组件库 * Ant Design(antd) 蚂蚁金服 * antd-mobile(移动端) * Material-UI(MUI) * React Bootstrap > https://react-bootstrap.github.io/ * React-vant(移动端) > https://react-vant.3lang.dev/ * axios > api地址:http://120.76.247.5:2003/api/goods ```bash 链接:https://easydoc.net/s/58934052 密码:4wSFKBYp ``` ## day2-3 ### 面试题 * 防抖与节流 > 都是为了优化性能的操作,相同点都是在同一时间周期内只执行一次 * 节流:在同一时间周期内触发多次操作时,只生效第一次 * 防抖:在同一时间周期内触发多次操作时,只生效最后一次 ### 知识点 * 高阶组件(HOC:High Order Component) > 高阶组件并不是一个组件,而是一个包装函数,它的返回值才是组件 * 注意事项 * 必须传入一个函数 * 必须返回一个函数 * 必须向下传递props * 应用场景 * 属性代理 * 提取公共代码 ```js function withNavigate(){ } ``` * 扩展运算符:`...` * 展开 ```js const arr = [10,20,30,40] Math.max(...arr);// 等效于 Math.max(10,20,30,40) const props = {a:10,b:20,c:30} // 复制对象 const newProps = {...props} ``` * 剩余 ```js const user = {name:'laoxie',age:18,gender:'male'} const {name,...attrs} = user;// name='laoxie',attrs={age:18,gender:'male'} function sum(){ // arguments let total = 0 for(let i=0;i{ return args.reduce((prev,item)=>prev+item,a) } sum(10,20) sum(10,20,30) sum(10,20,30,40) ``` * React提取公共代码 * 高阶组件 ```js class Home extends React.Component{ //state = { // list:[] //} //componentDidMount(){ // // 发起ajax请求(20行代码) //} render(){ const {list} = this.props; return
{list}
} } Home = withRequestList(Home) class List extends React.Component{ //state = { // list:[] //} //componentDidMount(){ // // 发起ajax请求(20行代码) //} render(){ const {list} = this.props; return
{list}
} } List = withRequestList(List) class Discover extends React.Component{ //state = { // list:[] //} //componentDidMount(){ // 发起ajax请求(20行代码) //} render(){ const {list} = this.props; return
{list}
} } Discover = withRequestList(Discover) // 利用高阶组件提取公共代码 function withRequestList(InnerComponent){ return class WrapperComponent extends React.Component{ state = { list:[] } componentDidMount(){ // 发起ajax请求(20行代码) } render(){ return } } } ``` * 自定义hook ```js function Home(){ //const [list,setList] = useState([]) //useEffect(()=>{ // // ajax请求(20行代码) //},[]) const list = useRequestList() // 其他代码 // ... return
{list}
} function List(){ //const [list,setList] = useState([]) //useEffect(()=>{ // // ajax请求(20行代码) //},[]) const list = useRequestList() // 其他代码 // ... return
{list}
} function Discover(){ //const [list,setList] = useState([]) // useEffect(()=>{ // // ajax请求(20行代码) //},[]) const list = useRequestList() // 其他代码 // ... return
{list}
} // 利用自定义hook提取公共代码 function useRequestList(){ const [list,setList] = useState([]) useEffect(()=>{ // ajax请求(20行代码) },[]) return list; } ``` * 路由传参与接收 * search传参 * 传参:通过?传递 * 接收:`const [params,setParams] = useSearchParams() ` * 动态路由传参 * 传参:通过url路径传参 * 接收:`const {id} = useParams()` * axios二次封装 > 利用axios.create()创建axios实例,并配合baseURL实现简化请求的效果 ### 练习 * 实现搜索框吸顶效果 ## day2-4 ### 面试题 * some与every * 如何给对象添加一个不可修改的属性 * 方案一:存储器属性之getter ```js const source = { age:18 } const user = { //age:18, // 存储器属性getter get age(){ return source.age }, // 存储器属性setter set age(newValue){ if(newValue>=18){ source.age = newValue }else{ throw new Error('年龄不能小于18') } } } user.age;// 读取:执行getter中的代码,得到18 user.age = 20; // 写入:执行setter中的代码,并传递值 ``` * 方案二:属性特性 ```js // 需求:设置一个不可修改的属性age const user = { age:18 } //Object.definePropery(target,key,descriptor) // target: 目标对象 // key: 对象的属性 // descriptor: 属性特性(值为Boolean) // * configurable 可配置性(writable与enumberalbe的总开关) // * writable 可写性 // * enumerable 可枚举性(是否可遍历) // * value 值属性的值 Object.defineProperty(user,'age',{ writable:false }) ``` > 通过传统方式**添加**的属性,属性特性默认为true;通过Object.definePropery()**添加**的属性,属性特性默认为false * 查看属性特性:Object.getOwnPropertyDescriptor() * 对象属性分类 * 值属性:拥有值的属性 * configurable * enumerable * writable * value * 存储器属性:getter & setter * configurable * enumerable * get * set ### 知识点 * 商品分类效果 * 接口:`get /api/goods?category=男士表` * 方案一:根据点击请求不同的数据进行渲染 * 方案二:动态路由 > 适合简单页面 * 方案三:子路由(嵌套路由) > 使用复杂页面 * ReactRouter Hooks * useParams() 获取动态路由参数 * useSearchParams() 获取?后的参数 * useNavigate() 获取路由跳转方法 * useLocation() 获取当前路由信息 * `` > 类似于原生标签中的template标签或DOM中的createDocumentFragment,简写:`<>` ## day2-5 ### 面试题 * 关注点分离 ```js class Home extends React.Component{ state = { // 功能1 datalist:[], newlist:[] } componentDidMount(){ // 功能1 request.get('/xxx').then(({data})=>{ this.setState({ datalist:data.data.result }) }) // 50 rows代码 request.get('/xxx2').then(({data})=>{ this.setState({ datalist:data.data.result }) }) } componentWillUnmount(){ // 功能1 cancel1() cancel2() } render(){ } } function Home(){ // 功能1 const [datalist,setDatalist] = useState([]) useEffect(()=>{ request.get('/xxx1').then(({data})=>{ setDatalist(data.data.result) }) return function(){ cancel1() } }) // 功能1 const [newlist,setNewlist] = useState([]) useEffect(()=>{ request.get('/xxx1').then(({data})=>{ setNewlist(data.data.result) }) return function(){ cancel2() } }) } ``` ### 知识点 * 全局状态管理 * 方案一:useReducer + context实现全局状态 * 封装Provider组件,用于向下共享数据 * 封装useUser,用于简化代码 * redux * 数据持久化 * 本地存储 ### 练习 * 购物车中的商品选择后才计算价格 * 实现数据持久化 ## day2-6 ### 知识点 * 数据持久化(persist) * 数据的更新(增删改)同步到本地存储(localStorage) * 刷新时从本地存储获取数据 * 鉴权 > 需要权限才可访问 * 页面访问权限:页面需要登录才可访问 * 功能权限:某些功能需要特定用户才可使用 > 按钮级别权限 * 数据权限:拥有权限的用户才可看到这些数据 * 页面访问权限: > 需求:用户登录后才能访问购物车页面,没登录时回退到登录页面 * 判断条件:用户访问购物车页面时,判断用户是否已登录 * 方案一: Hooks * 方案二:HOC * 用户体验 * 后台管理系统 > 对内:内部使用不公开的系统,特点是需要登录才能访问,对数据进行增删改查 * 常用组件 * 表格 * 表单 * UI组件库 * ant-Design * @ant-design/icons ```bash npm install antd ``` * 项目搭建 * 手动配置 * CLI工具 * CRA:create-react-app * 自定义webpack配置:react-app-rewired > 让我们在不修改CRA配置的基础上拥有webpack的配置权 1. 的根目录中创建`config-overrides.js`文件,并修改webpack配置 ```js module.exports = function(config){ // config: 原有webpack配置 } ``` > PS: react-app-rewired2.x 已经把所有配置方法移置到了customize-cra模块 ```js const {override,addDecoratorsLegacy,disableEsLint,useBabelRc,fixBabelImports} = require('customize-cra'); module.exports = override( addDecoratorsLegacy(), // 装饰器支持 fixBabelImports('import',{ libraryName: "antd", style: "css" }) ) ``` 2. 重写npm script ```bash "scripts": { - "start": "react-scripts start", + "start": "react-app-rewired start", - "build": "react-scripts build", + "build": "react-app-rewired build", - "test": "react-scripts test --env=jsdom", + "test": "react-app-rewired test --env=jsdom", "eject": "react-scripts eject" } ``` * Vite 1. 创建项目 ```bash npm create vite ``` 2. 安装依赖 ```bash npm install ``` 3. 启动项目 ```bash npm run dev ``` ### 练习 * Manage页面权限访问控制 > 只有登录后才能访问 * 共享用户信息 ## day2-7 ### 知识点 * redux: 全局状态管理 > redux与react为两个独立产品,他们之间没有直接联系 1. 安装 ```js npm i redux ``` 2. 核心API * 创建数据仓库:store ```js const store = createStore(reducer,state) ``` * store对象的方法 * getState() 获取state全局状态 * dispatch(action) 修改state * subscribe(callback) 监听,当state被修改时,callback会被自行 * 全局状态:state * 修改状态的方法:reducer > reducer必须为一个纯函数,必须返回一个新的state ```js const reducer = (state,action)=>{ switch(action.type){ } } ``` * 操作全局状态的命令:action > 格式:`{type,payload}` 3. 在react组件中操作redux全局状态 * 获取:`store.getState()` ```js // 在Manage.jsx中获取redux数据 ``` * 修改 ```js const action = {type:'login',payload:{}} store.dispatch(action) ``` * 监听 ```js store.subscribe(function(){ }) ``` * 利用自定义hook或高阶组件实现redux数据获取 * `withStore()` * 函数柯里化 > 利用多个函数收集不同参数,然后统一处理的方式 ```js withStore()()()(); function withStore(){ return function(){ return function(){ return function(){ } } } } ``` * 可选链操作符: `?.` ## day3-1 ### 面试题 * 箭头函数中如何获取实参(arguments) ```js const sum = (...args)=>{ } sum(1,2) sum(10,20,30) sum(5,10,15,20) const arr = [1,2,3,4,5] sum(...arr);// sum(1,2,3,4,5,6) ``` ### 知识点 * react-redux 1. 安装 ```bash npm i react-redux ``` 2. 使用Provider组件共享Redux状态 ```js ``` 3. 在组件中使用redux数据 * 高阶组件: connect() * hooks * useStore ```js const store = useStore() const {userInfo} = store.getState() ``` * useSelector (推荐) ```js const userInfo = useSelector(state=>state.userInfo) ``` * useDispatch ```js const dispatch = useDispatch() dispatch({type:'logout'}) ``` * 后台管理系统模块:商品管理 * 商品列表:Table * 展示商品数据 OK * 分页 OK > 刷新后保留分页与每页数量信息 * 添加 OK * 编辑 OK * 删除 OK * 批量删除 OK * 上下架 (练习) * 编辑:Form * 如何把ajax请求回的数据写入表单 * initialValues: 只有在初始化时生效,不适合ajax请求回的数据 * myform.setFieldsValue(values) ```js // 创建表单实例 const [myform] = Form.useForm() // 关联表单实例
``` * 统一发送token到服务器: axios拦截器(请求拦截+响应拦截) > axios二次封装统一实现 ### 练习 * 上下架 * 添加 ## day3-2 ### 面试题 * 如何取消ajax请求 * XMLHttpRequest * 设置超时时间 ```js const xhr = new XMLHttpRequest() // 取消ajax xhr.timeout = 5000 ``` * 调用abort手动取消 ```js xhr.abort() ``` * axios ```js // AbortController(推荐) const controller = new AbortController() request.get(url,{ signal:controller.signal }) // 取消ajax controller.abort() // CancelToken(不推荐) const source = axios.CancelToken.source(); request.get(url,{ cancelToken:source.token }) // 取消ajax source.cancel() ``` * React组件中在哪取消ajax请求 > 组件销毁后操作 * 类组件:componentWillUnmount * 函数组件: ```js useEffect(()=>{ return function(){ // 取消ajax } },[]) ``` ## day3-3 ### 面试题 * React组件刷新场景 * state改变 * props改变 ```js // Parent.jsx const Parent = ()=>{ const [num,setNumber] = useState(1) const [qty,setQty] = useState(1) return
} // Child.jsx // 当Parent的num发生改变时,也会导致Child刷新(如何优化?) const Child = (props)=>{ console.log('child') return
父组件传入数据:{props.qty}
} // 类组件优化 class Child extends React.PureComponent{ // shouldComponentUpdate(nextProps){} } // 函数组件优化: 等效于类组件中的PureComponent const Child = React.memo((props)=>{ console.log('child') return
父组件传入数据:{props.qty}
}) // 函数组件优化: React.memo()使用第二个参数后等效于shouldComponentUpdate const Child = React.memo((props)=>{ console.log('child') return
父组件传入数据:{props.qty}
},(props,nextProps)=>{ // props: 为当前组件props // nextProps: 将要更新的props }) ``` * React.memo()、useMemo()、PureComponent、shouldComponentUpdate的作用 ### 知识点 * react * react-redux * Provider * connect() * useStore() * useSelector() * useDispatch() * redux * 解决复杂数据共享问题 > reducer模块化 * combineReducers() * Action Creator: action构造器(一个创建action的地方) > 封装一个创建Action的函数 ## day3-4 ### 知识点 * token(令牌) Token 是服务端生成的一串加密后的字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个 Token 便将此 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次带上用户名和密码 目的:Token 的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮 * 如何使用: * 生成 1. 客户端使用用户名和密码登录 2. 服务端校验用户名和密码是否正确,如正确则生成 token(生成规则不定),然后返回给前端 3. 客户端接收到 token,并保存到本地(cookie,localStorage) * 校验 * 客户端以后的每次请求都在请求头中要携带 token * 服务端对每一次请求进行 token 验证(验证是否失效),校验通过则放行,不通过则拒绝 * jsonwebtoken * 生成: `jwt.sign(payload, secretOrPrivateKey, [options, callback])` > 加密的过程 * 校验: `jwt.verify(token, secretOrPublicKey, [options, callback])` > 解密的过程 ## day3-5 ### 面试题 * 在css当中如何定义和使用变量 ```css body{ --color:#58bc58; } .btn{ color:#58bc58 } .title:{ color:#58bc58 } ``` * const定义的对象,能不能修改属性 ```js const username = 'jingjing' const user = {username:'jingjing',age:36} user.age = 35; ``` * git stash缓存修改 * git stash 缓存修改结果 > 不能缓存新添加的修改,新添加需要git add后才可缓存 * git stash list 查看缓存列表 * git stash pop 把缓存列表中的最后一个移到工作目录并删除 ### 知识点 * 项目验收 * 时间:2022-10-17晚上 * 合并分支: git merge ```bash # 把dev分支合并到当前分支 git merge dev ``` * 解决BrowserRouter刷新后404的问题:不管请求什么地址,都响应index.html内容 ```js // 不管前端访问任意地址都会进入该中间件 // 解决404的问题:不管访问什么地址,都响应index.html内容 app.use((req,res)=>{ fs.readFile(path.join(__dirname,'public/index.html'),(err,content)=>{ res.set({'Content-Type':'text/html;charset=utf-8'}) res.send(content) }) }) ```