https://github.com/adrianhajdin/portfolio_website
【前端工程师面试宝典】学习说明_互联网校招面试真题面经汇总_牛客网 (nowcoder.com)
面试常考题目总结
-
react 生命周期
React 组件的生命周期有三个不同的阶段:
- 初始渲染阶段:*这是组件即将开始其生命之旅并进入 DOM 的阶段。
- 更新阶段:*一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染。这些只发生在这个阶段。
- 卸载阶段:*这是组件生命周期的最后阶段,组件被销毁并从 DOM 中删除
componentWillMount() – 在渲染之前执行,在客户端和服务器端都会执行
componentDidMount() – 仅在第一次渲染后在客户端执行。
componentWillReceiveProps**()** – 当从父类接收到 props 并且在调用另一个渲染器之前调用。
shouldComponentUpdate**()** – 根据特定条件返回 true 或 false。如果你希望更新组件,请返回true 否则返回 false。默认情况下,它返回 false。
componentWillUpdate**()** – 在 DOM 中进行渲染之前调用。
componentDidUpdate**()** – 在渲染发生后立即调用。
componentWillUnmount**()** – 从 DOM 卸载组件后调用。用于清理内存空间。
-
react 的优缺点
React的一些主要优点是:
- 采用组件化模式,声明式编码,提高开发效率和组件复用率
- 使用虚拟 dom 和优秀的 diffing 算法,尽量减少与真实 dom 的交互
- 使用范围广,可以开发 web 应用,也可以用 react navie 进行移动端开发,还可以用 react 360 进行 VR 开发
- 由于 JSX,代码的可读性很好
- 使用 React,编写UI测试用例变得非常容易
React的限制如下:
- React 只是一个 JS 库,而不是一个完整的框架
- 它的库非常庞大,需要时间来理解
- 新手程序员可能很难理解
- 编码变得复杂,因为它使用内联模板和 JSX
-
react 和 vue 的对比
-
real dom 和 vitual dom
什么是 dom?
DOM是用一颗逻辑树来表示一个文档,树的每个分支的终点都是一个节点,可以用特定的方式(编写JS、CSS、HTML)来改变这个树的结构,从而改变文档结构、样式或内容
什么是虚拟DOM? 虚拟DOM就是一个JS对象,通过对象的方式来表示DOM结构,通过事务处理机制,将多次DOM修改的结果一次性更新到页面上,减少DOM操作的次数,从而有效的减少页面渲染次数,减少修改DOM重绘重排的时间,提高渲染性能
React在内存中维护一个跟真实DOM一样的虚拟DOM树,再改动完组件后,会再生成一个新的虚拟DOM,React会将新的虚拟DOM和原来的虚拟DOM进行对比,找出两个DOM树的不同的地方(diff),然后在真实DOM上更新diff,提高渲染速度
-
类式组件 和 函数式组件
- 类组件有生命周期,函数组件没有
- 类组件需要继承 Class,函数组件不需要
- 类组件可以获取实例化的 this,并且基于 this 做各种操作,函数组件不行
- 类组件内部可以定义并维护 state, 函数组件为无状态组件(可以通过hooks实现)
- 函数式组件配合 hooks 使用
-
类式组件中的 this 的指向问题
class App extends React.Component { handleClick() { console.log(this);// undefined } render() { return <div onClick={this.handleClick} >点我</div>; } }
在 react中,事件处理函数中的 this 很容易丢失,我们有如下4种解决方案
- 箭头函数
- 构造函数中的
bind
- 事件绑定时的
bind
- 事件绑定时的匿名函数+箭头函数
(181条消息) 一文详解React事件中this指向,面试必备_爱笑的眼睛11的博客-CSDN博客_react 事件this
-
受控组件 和 非受控组件
-
React redux
什么是 Redux
专门针对 react 所设计的状态管理工具,顶层套一个 provider,底层的组件都可以使用顶层的数据
数据如何通过 Redux 流动?
- 首先,用户(通过View)发出 Action,发出方式就用到了 dispatch 方法。\
- 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State
- State 一旦有变化,Store 就会调用监听函数,来更新 View
Redux 遵循的三个原则是什么?
- 单一事实来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序
- 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示
- 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数
你对“单一事实来源”有什么理解?
Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序。
Redux 由以下组件组成
- Action – 这是一个用来描述发生了什么事情的对象,是把数据从应用传到store的载体,它是store数据的唯一来源,一般来说,我们可以通过 store.dispatch() 将 action 传递给 store
- Reducer – 这是一个确定状态将如何变化的地方,本质就是一个函数,它用来响应发送过来的actions,然后经过处理,把state发送给store
- Store – Store就是把action与reducer联系到一起的对象,整个程序的状态/对象树保存在Store中。
- View – 只显示 Store 提供的数据
-
Redux middleware
(189条消息) Redux中间件原理详解_daysRoc的博客-CSDN博客_redux中间件的原理
作用就是改造dispatch函数,当你应用了中间件,在触发一个action操作的时候,action操作就会经过先经过中间件,最终再形成dispatch(action)
常见的中间件:
- Redux-actions帮助我们简化了action代码和reducer代码
- redux-logger 记录所有action及其发送前后的state的日志,我们可以了解到代码实际运行时到底发生了什么
- redux-thunk:处理异步action
常规使用:createStore + applyMiddleWare,定义 myMiddleware中间件,放在applyMiddleware 方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。即可以在reducer中进行一些异步的操作
import { applyMiddleware, createStore } from 'redux'; import thunk from 'redux-thunk'; import createLogger from 'redux-logger'; const logger = createLogger(); const store = createStore( reducers, applyMiddleware(thunk, logger) //会按顺序执行 );
下面是createStore + applyMiddleWare的源码
const store = createStore(reducer, preloadedState, enchancer); // 如果没有中间件,正常触发一个action; // 如果有中间件的时候 creatStore内部的执行逻辑是这样的 // enchancer 就是你应用的中间件,调用applyMiddleware得到的组合中间件 function applyMiddleware(...middlewares) { return createStore => (...args) => { // 该创建的store还是要创建的,只传入了两个参数,没有中间件,得到的是正常的store const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } // 把getState、dispatch传给中间件 const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 下面方法返回来了一个函数数组,中间件被剥离到 // next => {} const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 再执行下面的,中间件就被剥离到 // action => {} dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } // 下面得到的结果是 // 假设中间件为 a b // a(b(store.dispatch)) return enchancer(createStore)(reducer, preloadedState); // 结合上面thunk的源码 ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState); } return next(action); };
-
React hook
React Hooks 是从 React 16.8 版本推出的新特性,目的是解决 React 的状态共享以及组件生命周期管理混乱的问题。React Hooks 的出现标志着,React 不会再存在无状态组件的情况,React 将只有类组件和函数组件的概念
hooks 为函数组件提供了状态,也支持在函数组件中进行数据获取、订阅事件解绑事件等等
- useState:通过 useState 为组件提供状态。useState 的参数是 state 的初始值,他只有在组件第一次渲染的时候会生效,他的返回值是一个数组,第一个是 state,第二个是设置state的函数。
const [count, setCount] = useState(0);
- useEffect:通常在useEffect中进行ajax请求,事件的绑定与解绑,设置定时器与清除等等
// useEffect第一个参数是一个回调函数,在里面进行业务逻辑代码的书写 // 第二个参数是依赖项数组,如果数组中的依赖发生变化,那么该副作用就会重新执行, // 如果不设置第二个参数,那么当该组件每渲染一次,副作用就会执行一次 useEffect(() => { console.log("useEffect副作用执行"); //用setTimeout模拟ajax请求 setTimeout(() => { setList(result); }, 3000); }, [list]);
- useCallback:用于缓存函数,第一个参数为要缓存的函数,第二个参数为依赖项数组,如果依赖发生了变化,那么就会生成一个新的函数;否则当组件重新渲染时,不会重新定义这个函数,而是会取缓存。
- useMemo:用于缓存函数的返回值,第一个参数为要缓存的函数(注意实际被缓存的是函数被执行过后的值),第二个参数为依赖项数组,如果依赖发生了变化,那么就会重新执行这个函数,得到新的返回值;否则当组件重新渲染时,不会重新执行这个函数,而是直接取被缓存的该函数的返回值。
-
React router
-
什么是React 路由?
React router是一个构建在 React 之上的强大的路由库,它有助于向应用程序添加新的屏幕和流。这使 URL 与网页上显示的数据保持同步。它负责维护标准化的结构和行为,并用于开发单页 Web 应用。 React 路由有一个简单的API
<switch> <route exact="" path="’/’" component="{Home}/"> <route path="’/posts/:id’" component="{Newpost}/"> <route path="’/posts’" component="{Post}/"> </route></route></route></switch>
-
为什么需要 React 中的路由?
Router 用于定义多个路由,当用户定义特定的 URL 时,如果此 URL 与 Router 内定义的任何 “路由” 的路径匹配,则用户将重定向到该特定路由。所以基本上我们需要在自己的应用中添加一个 Router 库,允许创建多个路由,每个路由都会向我们提供一个独特的视图
-
为什么React Router v4中使用 switch 关键字 ?
虽然
用于封装 Router 中的多个路由,当你想要仅显示要在多个定义的路线中呈现的单个路线时,可以使用 “switch” 关键字。使用时,标记会按顺序将已定义的 URL 与已定义的路由进行匹配。找到第一个匹配项后,它将渲染指定的路径。从而绕过其它路线。 列出 React Router 的优点
4.1 就像 React 基于组件一样,在 React Router v4 中,API 是 ‘All About Components’。可以将 Router 可视化为单个根组件(),其中我们将特定的子路由()包起来。
4.2 无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在 组件中。
4.3 包是分开的:共有三个包,分别用于 Web、Native 和 Core。这使我们应用更加紧凑。基于类似的编码风格很容易进行切换
React router v6 的新特性
有哪些新hooks,如何传递路由参数?
react-router v6新特性 - 简书 (jianshu.com)
现在一般都是用的 React router v6
重命名为
/* v5 */ <Switch> <Route exact path="/" component={Home} /> <Route path="/user/:id" render={(routeProps) => <User id={routeProps.match.params.id} />} /> </Switch> /* V6 */ <Routes> <Route path="/" element={<Home />} /> <Route path="user/:id" element={<User id={id} />} /> </Routes>
-
的新特性变更: 1.废弃exact 2.component/render被element替代 3.routeProps可以在element中直接获取 4.简化的路径匹配,仅支持动态:id样式参数和*通配符,不再支持RegExp
-
嵌套路由
1.支持相对路径 2.
标签支持嵌套,可以在一个文件内配置嵌套路由,便于统一管理路由 3.结合新API Outlet实现嵌套路由
import { HashRouter as Router, Routes, Route } from 'react-router-dom' import Home from '@/pages/demo/Home' import Foo from '@/pages/demo/Foo' import Bar from '@/pages/demo/Bar' import BarDetail from '@/pages/demo/BarDetail' import '@/assets/style/App.css' function App() { return ( <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="foo" element={<Foo />} /> {/* 嵌套路由场景:需要在Bar(父路由的组件)声明Outlet组件,用于渲染子路由 */} <Route path="bar" element={<Bar />}> <Route path=":id" element={<BarDetail />} /> </Route> </Routes> </Router> ) } export default App
- 新API:Outlet
注意:嵌套路由场景,需要在父路由的组件(Bar)使用Outlet组件,用于渲染子路由,类似于vue的router-view
import { Outlet } from 'react-router-dom' function Bar() { return ( <div> <div>Bar</div> {/* 有嵌套路由的场景需要使用 */} <Outlet /> </div> ) } export default Bar
- 嵌套路由可配置化:useRoutes代替react-router-config
同理,配置化使用嵌套路由,Outlet 也是必要的
import { useRoutes } from 'react-router-dom' import Home from '@/pages/demo/Home' import Foo from '@/pages/demo/Foo' import Bar from '@/pages/demo/Bar' import BarDetail from '@/pages/demo/BarDetail' import '@/assets/style/App.css' function App() { let element = useRoutes([ { path: '/', element: <Home /> }, { path: 'foo', element: <Foo /> }, { path: 'bar', element: <Bar />, children: [ { path: ':id', element: <BarDetail /> } ] } ]) return element } export default App
**注意:**入口文件需要添加Router包裹App组件,否则会报错 (可以理解为,useRoutes返回的相当于Routes标签下的内容)
import React from 'react' import ReactDOM from 'react-dom' import { HashRouter as Router } from 'react-router-dom' import App from '@/App' import '@/assets/style/index.css' ReactDOM.render( <React.StrictMode> <Router> <App /> </Router> </React.StrictMode>, document.getElementById('root') )
- 用useNavigate代替useHistory
/* v5 */ const history = useHistory() history.push('/home') history.replace('/home') history.goBack() history.goForward() history.go(2) /* V6 */ const navigate = useNavigate() navigate('/home') navigate('/home', {replace: true}) navigate(-1) navigate(1) navigate(2)
React router 中两种路由模式的区别?
React 16 有哪些新特性
location 和 history 的使用
在 react 组件的 componentDidMount 方法中打印一下 this.props,在浏览器控制台中查看输出如下,其中页面的 url 信息全都包含在 match 字段中
localhost:3000/app/knowledgeManagement/modify /STY20171011124209535/3/1507701970070/0/?s=1&f=7
- **match:**包含了具体的 url 信息,在 params 字段中可以获取到各个路由参数的值
- **history:**包含了组件可以使用的各种路由系统的方法,常用的有 push 和 replace,两者都是跳转页面,但是 replace 不会引起页面的刷新,仅仅是改变 url
- **location:**相当于 URL 的对象形式表示,通过 search 字段可以获取到 url 中的 query 信息,所以location.search 是获取 url 参数的方法 这里 state 的含义与 HTML5 history.pushState API 中的 state 对象一样。每个 URL 都会对应一个 state 对象,你可以在对象里存储数据,但这个数据却不会出现在 URL 中。实际上,数据被存在了 sessionStorage 中
diff 算法
diff算法探讨的就是虚拟DOM树发生变化后,生成DOM树更新补丁的方式、它通过对比新旧两颗虚拟DOM树的变更差异,将更新补丁作用于真实DOM,以最小的成本完成试图更新
JSX 语法
JSX 是 JavaScript XML 的简写。是 React 使用的一种文件,它利用 JavaScript 的表现力和类似 HTML 的模板语法。这使得 HTML 文件非常容易理解。此文件能使应用非常可靠,并能够提高其性能
浏览器只能处理 JavaScript 对象,而不能读取常规 JavaScript 对象中的 JSX。所以为了使浏览器能够读取 JSX,首先,需要用像 Babel 这样的 JSX 转换器将 JSX 文件转换为 JavaScript 对象,然后再将其传给浏览器。
学习资源
Youtube Traversy Media
Udemy **Brad Traversy** MERN eCommerce From Scratch
Udemy **Brad Traversy MERN Stack Front To Back: Full Stack React, Redux & Node.js**
Udemy **John Smilga MERN Stack Course 2022 - MongoDB, Express, React and NodeJS**
React 介绍
用于构建用户界面的JavaScript库
React Basic
-
1 Create React App
以前:下载 create-react-app
npm install -g create-react-app create-react-app myapp cd myapp npm start
现在:直接 npx create-react-app
npx是直接安装这个包所需的文件,并且立即执行,然后从磁盘上删除,不用占用内存
npx create-react-app myapp cd myapp npm start
(optional) run ceate-app online
-
2 项目结构
-
package.json
这个文件管理项目的依赖,相当于maven里的pom.xml文件,但是它里面还有一些命名。总之,跟Vue是一样的
build脚本:创建一个最优化的低内存版本,用于打包托管服务器
test脚本:确保一个文件里的代码做了期望的事情
-
index.html
manifest.json 指定了开始页面 index.html,一切的开始都从这里开始,所以这个是代码执行的源头。其中的
是项目的总容器,我们把这个 div 称为根 DOM 节点,在此 div 下的所有内容都由 React DOM 来管理<body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body>
-
index.js
src 文件存放的是项目的核心内容,也是我们主要的工作区域
index.js 是和 index.html 文件关联的唯一接口,属于项目入口的文件,所有的起点都在这里,统一做一些初始化工作,例如加载各种依赖、加载状态管理库等等
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; const root = ReactDOM.createRoot(document.getElementById('root')); 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();
-
App.js
react 基本上是用function来return html
import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App;
可维护的 React 程序之项目结构梳理:
https://zhuanlan.zhihu.com/p/39799393
Class vs Hook
Hook:编写组件的一个新方法
Class:是传统的常用的方法,更多人在用
学习路线 class -> hook
-
-
4 JSX 语法规则
-
文件名可以是 jsx 也可以是 js,不影响
-
return 里面必须是闭合的,要有一个根元素
-
组件名称必须大写
<App />
,不能用<app />
-
js 中想写 html 用圆括号,html 中想写 js 用花括号
-
类名要用
<div className = ""> </div>
-
如果要直接在标签里写 css,要用驼峰命名法,不能用横杠
<h3 style={{marginBottom:'30px',}}>Input Your Information</h3> <div style={{backgroundColor:'skyblue'}}> </div>
-
注释用
{/* */}
-
-
5 JSX Mapping Arrays
keys to mapping:
可以确定一个唯一的id
NOTE:
react 中的列表循环有且只有 map 可以使用,不能用 forEach 和 for
可以简写成一行
React 组件
-
1 函数组件和类组件
组件化开发:function 组件和 class 组件
Class Component (1.4 - 1.6)
Function Component (1.7 - 1.10)
- 函数式组件没有生命周期
- 函数式组件没有 this
- 函数式组件没有 state 状态
- 配合 hooks 使用
函数式组件写法一:
写法二:使用箭头函数
-
2 类组件中方法的 this 指向
-
2 组件之间通讯的三种方式
父子组件之间的调用
Father to Son
父子组件之间的传参(父传子)
Son to Father
父子组件之间的传参(子传父)
注意:无论是react还是vue,所谓子传父,真实在干活的都是父组件
如果想要在子组件里面修改变量:
- 在父组件里面定义 state hook 和
setNum
的函数 - 在传递给子组件的 props 里面加入
setNum = { setNum }
- 子组件可以用
porps.setNum( 789 )
来调用
Grandfather to Son
就是跨级组件传值的意思,一般使用上下文空间 context
- 创建上下文空间:
- 用
<NumContext.Provider>
和<NumContext.Consumer>
分别包裹爷孙组件 - 不管是使用还是读取,如果传参是两个以上,都用花括号
- 可以用 useContext 这个 hook,来简化代码
- 在父组件里面定义 state hook 和
-
3 受控组件和不受控组件
受控组件和不受控组件只存在于表单元素,所谓的受控组件就是表单元素的value需要通过state来定义
input 里面要有绑定的 value 和对应的 onchange 函数
不受控组件意味着表单元素的value无法通过state获取, 只能使用 ref useRef
-
4 Props 的使用
-
2 React State 和 setState
只有 class component 有state
Use of State
背景:当所有的东西都是 hardcoded,意味着代码里面没有动态的东西,而我们需要动态呈现
作用:local state,也可以简称 state,可以让我们在 html 中直接使用和利用 JavaScript 变量,每当这些变量发生变化时,react 会重新渲染我们看到的这个 html
位置:写在构造器 constructor 里面。我们可以实际访问和生成 state 的方式是通过constructor(),constructor() 方法是一个对 class component 都可用的方法,super() 对我们 react 组件代码的作用并不重要,调用 super() 只是调用任何其他 extend 的类的底层构造方法
Set State
作用:每当这些变量发生变化时,React 会 rerendering 我们的 html 页面
注意:你的 component 当 state 是一个完全不同的 object 在内存中时,才会重新渲染
下面理解一下react里面的对象、指针问题
所以下面这种方法没有作用
⭐️ 采用 setState() 的方法才有用
Callback:
二次 callback的作用是,希望 console.log(this.state) 可以显示更新后的值,所以第二个 () => {} 写的这个函数就是回调,一旦代码完成了,就执行这个回调,这将在 state 完全更新后运行
NOTE:
setState 的调用是一个非同步(non synchronous)的调用,这是一个异步调用 (asynchronous)
THREE WAYS To setState():
按钮二和按钮三都是独立定义了 onClick 函数,但是会有 “this” undefine 的问题
- 按钮二是通过
.bind(this)
来解决的 - 按钮三是通过箭头函数来规避了这个问题,但是后面要加上括号
如果想要函数传参的话:
Shallow Merge
当你传递一个对象给它时,它将寻找存在于当前 state object 的任何 key,然后把它更新为新的 key,其他的 key 保持不变。所以 shallow merge 的意思就是它只更新被传递给它的 key and value,其他的东西都会以同样的方式保留
- 按钮二是通过
-
7 React Hooks
定义:hook 就是钩子,生命周期函数,只能用在函数组件的最顶层
useState hook
log出来可以发现
useState()
定义的是一个数组,前一个数是变量,后一个是函数于是可以把定义的变量拆解为数组
[ verb, setVerb ]
useEffect hook
可以模拟 mounted updated beforeDestory (数据请求 检测数据更新 垃圾回收) 三个生命周期
-
模拟 mounted
当页面第一次加载完成时运行,一般用来写 ajax
-
模拟 updated
检测哪个数据更新了,第一个参数是 function,第二个参数是想要检测的变量们的数组
如果所有的变量都想检测,可以直接删掉数组不填
-
模拟 beforeDestory
一般在这个阶段处理脏数据或者垃圾回收
useContext hook
用于跨域组件的传值
-
-
8 虚拟 DOM 和 diff 算法
React Hooks
React Redux
-
1 Intro
简介
专门针对 react 所设计的状态管理工具,可以理解为把需要多个组件共享的变量全部存储在一个对象里面。顶层套一个 provider,底层的组件都可以使用顶层的数据
npm i redux react-redux
数据如何通过 Redux 流动?
- 首先,用户(通过View)发出 Action,发出方式就用到了 dispatch 方法
- 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State
- State 一旦有变化,Store 就会调用监听函数,来更新 View
Redux 遵循的三个原则是什么?
- 单一事实来源:Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序。
- 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示
- 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数
使用情景:
- 大型开发,多个界面间的共享问题
- 比如用户的登录状态、拿到和保存 token、用户名称、头像、地理位置信息等等
- 比如商品的收藏、购物车中的物品(在很多页面都有添加购物车的功能)
- 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
简单使用
需要在项目里新建文件夹 store,里面有
- 左:index.js (仓库的入口文件)
- 右:reduce.js (创建初始状态,并导出一个函数)
修改组件
- 修改 src/index.js 添加 provider
- 修改 app componant,添加 connect,定义状态映射(将 reducer 中的 state 映射成 props,让开发者可以在组件中使用 props.num 去调用 state 中的 num),传入参数props
-
2 Redux 使用的简单 demo
创建 action 文件夹(因为在真实开发中可能会发送很多个 action)
写一个简单 action 构建函数
创建 reducers 文件夹,写一个简单 reducer
创建 store 文件夹,构建 store
在home使用上面的 store
效果:
-
2 Redux 四大组件
Redux 由以下组件组成
-
Action
- 这是一个用来描述发生了什么事情的 JavaScript 对象,是把数据从应用传到 store 的载体,它是 store 数据的唯一来源,只是描述了有事情要发生,并没有描述如何去更新
- 我们可以通过 store.dispatch() 将 action 传递给 store
- Action 是个动作对象,对象内部必须要有 type 和 data 属性,type 通常是字符串,表示动作执行的类型,data 表示跟本次动作执行相关的数据。我们在项目中更喜欢用 action creator 函数
// 1. action { type: 'string', info: {...}, isLoading: true } // 2. action creator function function addAction(params) { // 返回一个 action 对象 return { type: 'string', info: {...}, isLoading: true } }
-
Reducer
- 这是一个确定状态将如何变化的地方,本质就是一个函数,它用来响应和处理发送过来的 old state + actions,然后经过处理,把 new state 发送给 store,reducer 是真正做事的人
- Reducer 函数接受两个参数:old state,action
- 在 Reducer 函数中,一定要 return 返回值,就是 new state 的,这样 store 才能接收到
const initState = {...}; rootReducer = (state = initState, action) => {... return {...}};
- 这是一个确定状态将如何变化的地方,本质就是一个函数,它用来响应和处理发送过来的 old state + actions,然后经过处理,把 new state 发送给 store,reducer 是真正做事的人
-
Store
Store 就是把 action 与 reducer 联系到一起的对象,整个程序的状态/对象树保存在Store 中,主要职责:
- 维持应用的 state
- 提供 getState() 获取 state
- 提供 dispatch() 发送 action
- 通过 subscribe() 来注册监听
- 通过 subscribe() 返回值来注销监听
import {createStore} from "redux"; const store = createStore(reducer);
-
View
通过 getstate() 来显示 Store 提供的数据
-
-
3 mapState vs mapDispatch
mapState:状态映射,将 reducer 中的 state 映射成 props,让开发者可以在组件中使用 props.num 去调用 state 中的 num
mapDispatch:事件派发映射,将 reducer 中的事件映射成 props,让开发者可以在组件中使用 props.add() 去实现 num 的累加
注意:左图的reduce.js文件里面,比起 if 判断,一般还是常用 switch
React Router
-
0 SPA 单页面应用程序简介
单页面应用程序 single page application,整个应用只有一个完整的 html 页面,但有多个组件,点击页面的链接不会刷新页面,只会做页面的局部更新,数据都需要通过 ajax 请求获取,并在前端异步展现
-
1 安装使用 react-router-dom
react-router-dom 是 react 的一个插件库,目前最新的是 v6 版本
npm install react-router-dom@6 yarn add react-router-dom@6
-
2 路由配置 History Hash
react-router-dom 中有两种模式:
- History 模式:BrowserRouter,使用 h5 的 history.pushState API 来实现
- Hash 模式:HashRouter,使用 URL 的哈希值来实现,url 里面会带有井号 #,直接打包就可以放到线上
History 模式
import { BrowserRouter, Routes, Route, Link } from "react-router-dom" import Home from './pages/home' import About from './pages/about' function App() { return( <BrowserRouter> {/* 点击跳转 */} <Link to='/'>home</Link> <Link to='/about'>about</Link> {/* 路由出口位置 */} <Routes> <Route path='/' element={<Home />} /> <Route path='/' element={<Home />} /> </Routes> </BrowserRouter> ) } export default App;
HashRouter 模式
import { HashRouter, Routes, Route, Link } from "react-router-dom" import Home from './pages/home' import About from './pages/about' function App() { return( <HashRouter> {/* 点击跳转 */} <Link to='/'>home</Link> <Link to='/about'>about</Link> {/* 路由出口位置 */} <Routes> <Route path='/' element={<Home />} /> <Route path='/about' element={<About />} /> </Routes> </HashRouter> ) } export default App;
-
3 核心组件介绍 Link Routes Route
Link
作用:用于指定导航链接,完成路由跳转
**语法说明:**组件通过 to 属性指定路由地址,最终会渲染为 a 链接元素
<Link to='/'>home</Link>
Routes
**作用:**提供一个路由出口,满足条件的路由组件会渲染到组件内部
<Routes> <Route/> <Route/> </Routes>
Route
**作用:**用于指定导航链接,完成路由匹配
**语法说明:**path 属性指定匹配的路径地址,element 属性指定要渲染的组件
<Route path='/' element={<Home />} />
-
4 事件跳转 useNavigate
作用:通过 js 编程的方式进行路由跳转,比如从登录页面跳到关于页
import { useNavigate } from 'react-router-dom' function Login () { const navigate = useNavigate() const goAbout = () => { navigate('/about') } return ( <div> login <button onClick={goAbout}> jump to about page</button> </div> ) }
注意:如果在跳转时不想加历史记录,就是点左上角返回的时候不会再回去,可以添加额外参数 replace:true
navigate('/about',{replace: true})
-
5 跳转携带参数 useSearchParams useParams
useSearchParams
params是一个对象,对象里面有一个 get 的方法,用来获取对应的参数,把参数的名称作为 get 方法是参数传过来
// 传参 Login.js navigate('/about?id=1001&&name=mike') // 取参 About.js import { useSearchParams } from 'react-router-dom' let [params] = useSearchParams() let id = params.get('id') let name = params.get('name')
useParams
// 先在 App.js 配置占位路由 <Route path='/about/:id' element={<About />} /> // 传参 Login.js navigate('/about/1001') // 取参 About.js let params = useParams() let id = params.id
- 发起签到的时候报错
- 人脸识别签到,没有传照片退出的时候,要显示签到失败
- 学生签到之后要查看到记录
-