React 是一个用于构建用户界面的 JavaScript 库。
声明式 组件化 随学随用
npx create-react-app +项目名称
。yarn start
or npm start
。[email protected]
版本新添加的命令,用来简化 npm 工具包的使用流程。1.因为React.createElement()
创建 React 元素的问题:繁琐/不简洁;不直观,无法一眼看出所描述的结构;代码不容易维护!所以有JSX
2.JSX 是 JavaScript XML 的简写,表示可以在 JavaScript 代码中写 XML(HTML) 格式的代码。
优势:声明式语法更加直观,与 HTML 结构相同,降低了学习成本,提高了开发效率,JSX 是 React 的核心之一。
3.注意点
3.1. 必须有 1 个根节点,或者虚拟根节点 <>>
、
。
3.2. 属性名一般是驼峰的写法且不能是 JS 中的关键字,例如 class 改成 className,label 的 for 属性改为 htmlFor
,colspan 改为 colSpan
。
3.3. 元素若没有子节点,可以使用单标签,但一定要闭合,例如 。
3.4. [email protected]
之前需要先引入 React 才能使用 JSX(这个也好理解,因为 JSX 最后还是要被转成 React.createElement()
的形式)。
3.5. 换行建议使用 ()
进行包裹,防止换行的时候自动插入分号的 Bug。
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.querySelector("#root")).render( )
(1)函数组件
本质上来说就是一个 JS 函数必须以大写字母开头,必须有返回值函数组件中this指向undefined
(2)类组件
使用ES6语法中的class创建的组件类组件应该继承 React.Component
父类
1.柯里化:通过函数调用继续返回函数的形式,实现多次接收参数最后统一处理的函数编码形式
state = {
count: 0,
}
handleClick() {
// 这里的 this 指向是什么?那就看是谁调用的!
return () => {
console.log(this.state.count)
}
}
render() {
return (
计数器:{this.state.count}
)
}
}
2.箭头函数中的 this 指向“外部”,即 render 函数,而 render 函数中的 this 正是组件实例
class App extends Component {
state = {
count: 0,
}
handleClick() {
console.log(this.state.count)
}
render() {
return (
计数器:{this.state.count}
)
}
}
3.使用 bind
class App extends Component {
state = {
count: 0,
}
handleClick() {
console.log(this.state.count)
}
render() {
return (
计数器:{this.state.count}
)
}
}
4.通过赋值语句往实例上面添加一个箭头函数
class App extends Component {
state = {
count: 0,
}
handleClick = () => {
console.log(this.state.count)
}
render() {
return (
计数器:{this.state.count}
)
}
}
5.在构造函数中再创建一个实例方法,和原型方法公用一个函数体
constructor() {
super()
this.state = {
count: 0,
}
// 1. 往实例自身上又挂载了一个 handleClick 函数
// 2. 此函数的函数体是通过原型上 handleClick 函数生成的新函数
// 3. 并把原型上 handleClick 函数中的 this 通过 bind 绑定为了 this,而这里构造函数中的 this 正是实例对象
// 4. 其实点击的时候调用的是这个构造函数 handleClick(就近原则),而这个构造函数中的 handleClick 执行的是原型上的 handleClick 的函数体
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log(this.state.count)
}
render() {
return (
计数器:{this.state.count}
)
}
}
例如:salary={this.state.salary}
函数组件 接收{props.salary}
类组件 接收{this.props.salary}
通过自定义事件
子onClick={this.props.getMsg }
夫getMsg={this.getChildMsg}
1.一般先封装Context
import { createContext } from "react";
export const Context = createContext();
指定默认值
createContext({ age: 88, })
2.祖先组件通过
配合 value 属性提供数据
3.后代组件通过
配合函数获取数据
import React, { Component } from 'react'
import { context } from './App'
export default class B extends Component {
render() {
return (
{(value) => {
return (
{value.money}
)
}}
)
}
}
4.另一种获取数据的方式
import React, { Component } from 'react'
import { Context } from '../../context'
export default class B extends Component {
// 声明一个静态属性 contextType 等于 Context 对象
static contextType = Context
// 然后就可以通过 this.context 拿到传递过来的数据啦
render() {
return B {this.context.age}
}
}
// children 是一个特殊的 prop,上面的写法和下面等价,当内容比较多的时候,下面的写法更加直观
我是子节点
//也可以当作vue中作用域插槽来使用
function Hello(props) {
return 该组件的子节点:{props.children}
}
对于组件来说,props 是外来的,无法保证组件使用者传入数据的格式正确,如果传入的数据格式不对,可能会导致组件内部报错,而组件的使用者不能很明确的知道错误的原因。
如何对 props 进行校验
prop-types
包。组件名.propTypes = {}
来给组件的 props 添加校验规则。PropTypes
对象来指定。static propTypes={
colors:PropTypes.array
}
常见校验规则
{
// 常见类型
fn1: PropTypes.func,
// 必选
fn2: PropTypes.func.isRequired,
// 特定结构的对象
obj: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
}
默认值
import React, { Component } from 'react'
class Test extends Component {
render() {
const { age = 18 } = this.props
return {age}
}
}
export default Test
setState()
方法来更新数据,表现是异步的。setState
会带来性能问题。1.通过 setState 第二个参数可以立即拿到更新后的数据。
this.setState({}, () => {
console.log('这个回调函数会在状态更新后立即执行')
})
2.推荐使用 setState((preState) => {})
语法
这种语法依旧是异步的,不同的是通过 preState 可以获取到最新的状态。
[email protected]
以后,class 组件(提供状态和生命周期) + 函数组件(展示内容),Hooks(提供状态和生命周期) + 函数组件(展示内容),也可以混用这两种方式,即部分功能用 class 组件,部分功能用 Hooks + 函数组件。作用:为函数组件提供状态和修改状态的方法。
组件第 1 次渲染
useState(0)
将传入的参数作为初始状态值,即:0。组件第 2 次渲染
setCount(count + 1)
来修改状态,因为状态发生改变,所以,该组件会重新渲染。useState(0)
,此时 React 内部会拿到最新的状态值而非初始值,比如该案例中的最新状态值为 1。强调:useState 的初始值(参数)只会在组件第一次渲染时生效,也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件内部会记住每次更新后的最新状态值!
useState(回调函数)
,回调函数的返回值就是状态的初始值,该回调函数只会触发一次。
那组件或一般函数的副作用是什么呢?
a,组件的副作用:对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用,比如手动修改 DOM、数据(AJAX)请求、localStorage 操作等。
b,函数的副作用:如果一个函数修改了其局部环境之外的数据,那么它就被称为有副作用。
// 触发时机:第一次渲染会执行,任何数据变化导致组件更新时执行,相当于 componentDidMount + ComponentDidUpdate
useEffect(() => {})
// 触发时机:只在组件第一次渲染时执行,相当于 componentDidMount
useEffect(() => {}, [])
// 触发时机:第一次渲染会执行,当 count 变化时会再次执行,相当于 componentDidMount + componentDidUpdate(判断)
useEffect(() => {}, [count])
useEffect 可以返回一个函数,这个函数称为清理函数,在此函数内用来执行清理相关的操作(例如事件解绑、清除定时器等)。
清理函数的执行时机
a,useEffect 的第 2 个参数不写或写了一个有依赖项的数组,清理函数会在下一次副作用回调函数调用时以及组件卸载时执行,用于清除上一次或卸载前的副作用。
b,useEffect 的第 2 个参数为空数组,那么只会在组件卸载时会执行,相当于组件的 componetWillUnmount
。
建议:一个 useEffect 只用来处理一个功能,有多个功能时,可以使用多个 useEffect。
useEffect(() => {
async function fetchMyAPI() {
let url = 'http://something/' + productId
const response = await myFetch(url)
}
fetchMyAPI()
}, [productId])
{ current: null }
。 const xxxRef = useRef(null)
提供的数据。React.createContext
函数创建的对象。
提供的 value 数据。import { useContext } from 'react'
import { Context } from './countContext'
export default function Child() {
const value = useContext(Context)
return (
Child
{value.count}
)
}
Redux 是一个全局状态管理的 JS 库
a,action(动作):描述要做的事情(要干啥)。
export const increment = (payload) => ({
type: 'INCREMENT',
payload,
})
export const decrement = (payload) => ({
type: 'DECREMENT',
payload,
})
b,reducer(函数):更新状态(怎么干)。
export default function counter(state = 10, action) {
// 处理各种各样的 action
switch (action.type) {
case 'INCREMENT':
return state + action.payload
case 'DECREMENT':
return state - action.payload
default:
// 记得要有默认返回的处理
return state
}
}
c,store(仓库):整合 action 和 reducer(谁来指挥)。
一个应用只有一个 Store。
b,创建:const store = createStore(reducer)
。
c,获取数据:store.getState()
。
d,更新数据:store.dispatch(action)
。
其他 API。
a,订阅(监听)状态变化:const unSubscribe = store.subscribe(() => {})
,注意要先订阅,后续的更新才能被观测到。
b,取消订阅状态变化:unSubscribe()
。
// store: 整个数据的仓库,负责关联 reducer 和 action
//通过 store 对象可以给 reducer 分配 action
import { createStore } from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
export default store
纯函数是函数式编程中的概念,对于纯函数来说有一个很重要的特点:相同的输入总是得到相同的输出。
纯函数常具有以下特点。
a,不得改写参数,不能使用全局变量。
b,不能调用 Date.now() 或者 Math.random() 等不纯的方法,因为每次会得到不一样的结果。
c,不包含副作用的处理,副 作用:AJAX 请求、操作本地数据、或者操作函数外部的变量等。
好处:代码简洁、方便测试、方便性能优化。
为什么说纯函数呢?因为 reducer 要求自身就必须是一个纯函数。
import ReactDOM from 'react-dom'
import App from './App.js'
import store from './store/store.js'
import { Provider } from 'react-redux'
// 通过 Provider 提供 store 供其他组件内部使用
ReactDOM.render(
,
document.querySelector('#root')
)
// 用了 react-redux 下面手动触发更新的方式就没用了
/* store.subscribe(() => {
ReactDOM.render( , document.querySelector('#root'))
}) */
✍ 一旦使用了 react-redux,获取和更新数据的方式就变化了,要按照这个库的要求来。
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { increment, decrement } from './store/actions'
const App = () => {
const count = useSelector((state) => state)
const dispatch = useDispatch()
return (
{count}
)
}
export default App
解决:使用 Redux 中的 combineReducers({ counter: counterReducer, user: userReducer })
函数。
import { combineReducers } from 'redux'
function counter(state = 10, action) {
switch (action.type) {
case 'INCREMENT':
return state + action.payload
case 'DECREMENT':
return state - action.payload
default:
return state
}
}
function user(state = { name: 'ifer', age: 18 }, action) {
switch (action.type) {
case 'UPDATENAME':
return {
...state,
name: action.payload,
}
default:
return state
}
}
export default combineReducers({
counter,
user,
})
在 store 目录中创建 actionTypes.js
或 constants.js
文件。
使用常量创建 ActionType 并导出。
命名推荐:模块_动作
,比如:
COUNTER_INCREMENT
表示计数器模块中的 INCREMENT 动作。TODOLIST_ADD
表示 TodoList 案例中 ADD 动作。LOGIN_GETCODE
表示登录模块中获取验证码的动作,LOGIN_SUBMIT
表示登录模块中的提交功能。PROFILE_GETINFO
表示个人资料模块中的获取信息动作;PROFILE_UPDATEINFO
等。哪里需要用到就按需导入。
actionTypes.js
export const COUNTER_INCREMENT = 'COUNTER_INCREMENT'
export const COUNTER_DECREMENT = 'COUNTER_DECREMENT'
export const USER_UPDATENAME = 'USER_UPDATENAME'
actions.js
import { COUNTER_INCREMENT, COUNTER_DECREMENT, USER_UPDATENAME } from './actionTypes'
export const increment = (payload) => ({
type: COUNTER_INCREMENT,
payload,
})
export const decrement = (payload) => ({
type: COUNTER_DECREMENT,
payload,
})
export const updateName = (payload) => ({
type: USER_UPDATENAME,
payload,
})
Redux 中间件执行时机:在 dispatching action 和 到达 reducer 之间。
a,没有中间件:dispatch(action) => reducer
。
b,使用中间件:dispatch(action) => 执行中间件代码 => reducer
。
原理:封装了 Redux 的 dispatch 方法。
a,没有中间件:store.dispatch()
使用的是 Redux 库自己提供的 dispatch 方法,用来发起状态更新。
b,使用中间件:store.dispatch()
使用的是中间件封装处理后的 dispatch,但是最终还是会调用 Redux 库自己提供的 dispatch 方法
`redux-thunk` 中间件可以处理函数形式的 action,而在函数形式的 action 中就可以执行异步操作代码,完成异步操作。
yarn add redux-thunk
。yarn add redux-devtools-extension
。import { createStore, applyMiddleware } from 'redux'
import reducer from './reducers'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
SPA: Single Page Application
单页面应用程序,整个应用中只有一个页面(index.html)。
MPA : Multiple Page Application
多页面应用程序,整个应用中有很多个页面(*.html)。
优势:页面响应速度快,体验好(无刷新),降低了对服务器的压力。
a,传统的多页面应用程序,每次请求服务器返回的都是一整个完整的页面。
b,单页面应用程序只有第一次会加载完整的页面,以后每次请求仅仅获取必要的数据。
缺点:不利于 SEO 搜索引擎优化。
a,因为爬虫只爬取 HTML 页面中的文本内容,不会执行 JS 代码。
b,可以通过 SSR(服务端渲染 Server Side Rendering)来解决 SEO 问题,即先在服务器端把内容渲染出来,返回给浏览器的就是纯 HTML 内容了。
现代的前端应用大多都是 SPA,也就是只有一个 HTML 页面的应用程序,因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。
为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生,功能:让用户从一个视图(页面)导航到另一个视图(页面)。
yarn add [email protected]
react-router-dom
这个包提供了三个核心的组件。 import { HashRouter, Route, Link } from 'react-router-dom'
HashRouter
包裹整个应用,一个项目中只会有一个 Router。
App
发现音乐
Route
指定路由规则。 // 在哪里写的 Route,最终匹配到的组件就会渲染到哪里
HashRouter
和 BrowserRouter
,用来包裹整个应用,一个 React 应用只需要使用一次。http://localhost:3000/#/first
),是通过监听 window 的 hashchange
事件来实现的。http://localhost:3000/first
),是通过监听 window 的 popstate
事件来实现的。 // 使用时建议通过 as 起一个别名,方便修改
import { HashRouter as Router, Route, Link } from 'react-router-dom'
hashchange
或 popState
监听到了地址栏 url 的变化。Link
组件最终会渲染成 a 标签,用于指定路由导航。
a,to 属性,将来会渲染成 a 标签的 href 属性。
b,Link
组件无法实现导航的高亮效果。
NavLink
组件,一个更特殊的 Link
组件,可以用于指定当前导航高亮。
a,to:用于指定地址,会渲染成 a 标签的 href 属性。
b,activeClass:用于指定高亮的类名,默认 active
。
c,exact:精确匹配,表示必须精确匹配类名才会应用 class,默认是模糊模糊匹配。
/
能够匹配所有路由组件,因为所有路由组件都是以 /
开头的,一般来说,如果路径配置了 /
,往往都需要配置 exact 属性。
。Route
包裹在一个 Switch
组件中,这样只会渲染第一个匹配到的组件,往往是我们期望的。Switch
组件配合不带 path 属性的 Route 组件能实现 404 效果,即便不需要实现 404,也可以用 Switch 包裹来提升性能。props.history.push('/comment')
飙升榜
// props.match.params.id 或者通过 useParams()
飙升榜
import qs from 'qs'
export default function RankingList(props) {
const { id } = qs.parse(props.location.search.slice(1))
return (
<>
props 获取参数: {id}
>
)
}