React快速上手
React个人笔记
1. 介绍
省略
2. 项目创建
-
前提条件:
node>14,windows>7 -
创建项目三种方法
npx create-react-app my-test
npm init react-app my-test
yarn create react-app my-test
如果要使用ts,则
npx create-react-app my-test --template typescript
-
项目目录与vue类似(省略)
在src中是组件,并没有单独的文件夹。其中reportWebVitals.ts
用于做性能测试,setupTests.ts
做测试。在public文件夹下manifest.json
文件用于其它端的app开发,类似于uniapp。 -
react同样也是基于webpack的,但是当中没有类似于vue中的vue.config.js,如果我们要进行webapck的相关配置,就可以使用
npm run eject
将其分离出来,会出现一个config
文件夹,里面就会有webpack.config.js
以及一下其它的相关配置文件,此时就可以进行个性化定制。比如我们想使用@符号来表示src文件夹,那么就需要在webpack.config.js文件中找到alias(别名) 配置项进行相关配置
alias:{ '@':path.resolve('src') }
如果使用的是ts,同时也要在tsconfig.js文件中启用@标识src文件夹
3. JSX语 法
我们可以直接声明一个类似HTML标签的变量赋值给JS变量,这个HTML标签类型的变量,就叫做JSX语法。jsx可以生成React元素,如果是ts文件则是tsx文件
注意事项
-
根标签包裹,可以使用div,也可以使用空标签(建议),JS变量如果是多行,建议外面包裹大括号
-
在JSX中写注释,注意是JS的注释,也就是js的注释写在大括号里面
-
JSX中
合法的JS表达式
需要写在大括号里面,类似于vue中插值语法,不过vue中是两个大括号,在react中是一个大括号。 -
在JSX中因为class是类的关键字,因此如果想要在jsx中给一个标签添加类名,则要使用
className
而不能使用class。 -
JSX表达是用来描述类似HTML + JS灵活操作的组合
const Ele = ( <h1 className="red">你好,我是赵日天 </h1> ) // 本质上来讲并不是直接创建html,而是创建一个虚拟DOM const ele = React.createElement( 'h1', { className:'red', }, children:'你好,我是赵日天' )
条件判断以及循环
在vue中有v-for和v-if,但是在react中却不同。react中接近原生js,因此可以直接使用原生的js来实现
-
循环,循环需要使用map表达式
function App(){ const arr = ['apple','orange','peach','watermalon'] } return ( <h1>水果列表</h1> arr.map(item=><div>item</div>) )
-
直接写if进行条件判断,如果符合条件,则return对应的视图,否则返回空,简短的可以写三目运算符更为方便。
4.组件
react中组件定义方式有两种:函数组件
和类组件
函数组件
函数组件要使用大驼峰
的命名方式来定义,可以使用箭头函数或者普通函数方式。
使用方法:
const MyApp = ()=>{
const info = <h1>你好,我是函数组件</h1>
return (
<>
{info}
</>
)
}
类组件
其中render函数是必不可少的,在render函数内部必须要返回一个JSX代码。
如果说当前返回的并没有涉及到标签,那建议将文件格式为js,直接返回函数即可。
render函数本质上来讲就是react的一个生命周期钩子函数
使用方法:
import React from 'react'
class App extends React.Component{
// 必须要有render方法
render(){
const info = <div>你好,我是类组件</div>
return (
<>
<h1> 测试</h1>
{info}
</>
)
}
}
export default App
函数组件和类组件的区别
- 组件的定义方式不同
- 生命周期不同,函数组件没有生命周期
- 副作用操作执行不同:class通过生命周期函数,函数组件用hooks的useEffect
- state的定义、读取、修改方式不同,函数组件用hook的useState
- 类组件有this,函数组件没有
- 类组件有实例,函数组件没有
- ref使用不同,类组件可以获取子组件的实例,函数组件没有实例
官网推荐使用函数组件,使用Hooks编程
5.React State响应式数据对象
state是类组件的内置组件,用于类组件内部数据更新,也叫响应式对象。state就是组件描述某种现实情况的数据,由组件自己设置和更改,也就是说组件自己维护,使用state的目的就是为了在不同状态下控制组件显示,state是私有的,完全受控于当前组件。
注意:React无法直接将对象渲染到页面上,需要转化成字符串或者数组类型。
interface isState {
msg:string,
date:Date()
}
// Component中两个参数,第一个是props,第二个是state的类型
class App extends Component<any,isState> {
constructor(props:any){
super(props)
this.state = {
msg:'你好',
date:new Date()
}
}
render(){
return(
<>
<h2>你好</h2>
<h3>{this.state.msg}</h3>
<h3>{this.state.date.toString()}</h3>
</>
)
}
}
后面就可以直接不写constructor
,直接写成
[public] state = {
msg:'你好',
date: new Date()
}
注意
-
修改响应式对象不能直接使用this.state进行修改,因为这样不会进行视图的刷新,需要使用
this.setState()
方法进行修改,这个方法是内置的,继承自React.Component
,包括自增、自减
等操作,因为这样也是直接进行修改的。 -
state的更新是异步的,会采集每次更新修正,以采集最后一次为主,并通知页面更新。比如我们写了多个setState()来修改state中的变量,修改的方法前面的会被覆盖,只会以
最后一次
为准。如果想要多次的setState都生效,那么我们可以拷贝一个副本并返回。也就是如果我们直接操作对象,那么它们使用的是一个地址,会被影响,所以我们就是用回调函数,这样每次都会返回一个新的对象,不会互相影响。this.setState((state,props)=>{ return { count:this.state.count + 1 } })
-
对数组的修改需要注意,比如向数组中添加元素,push返回的是数组的长度,可以直接使用扩展运算符,返回一个新数组,或者也可以先给state进行push,之后再交给arr。
setState还支持第二个参数,第二个参数就类似于nextTick,也就是DOM更新完成了的阶段。
6.事件绑定
事件绑定采用小驼峰的命名方式
ES5的事件绑定
es5中没有箭头函数,大部分都是借助bind等来改变this的指向
在严格模式下,DOM上绑定事件,这个事件的回调函数中的this是undefined
class App extends Component<any,isState>{
constructor(props:any){
super(prop)
this.state = {
msg:'你好,世界',
count:0
}
this.addCount = this.addCount.bind(this)
}
//累加行为
addCount(){
this.setState((state)=>{
count:state.count + 1
})
}
//累减行为
subCount(){
//如果不显式的传递参数,react会自动接受一个 e 作为事件对象(这个是被react封装过的)
this.setState((state)=>{
count:state.count - 1
})
}
render(){
return (
<>
<h1>this.state.msg</h1>
<h2>当前计数是:{this.state.count}</h2>
<button onClick={this.addCount}>点击增加</button>
<button onClick={this.subCount.bind(this)}>点击减少</button>
</>
)
}
}
ES6语法的事件绑定
class App extends Component<any,isState> {
state = {
msg:'你好,世界',
count:0
}
// ES6及其之后可以使用箭头函数,因为箭头函数没有自己的this,它会向上层去访问
addCount=()=>{
this.setState({
count:this.state.count + 1
})
subCount(){
this.setState({
count:this.state.count - 1
})
}
// render是React的生命周期钩子,会在初始化和state中变更时触发,类似于vue中的update
render(){
return (
<>
<h1>当前的计数是:{this.state.count}</h1>
<button onClick={this.addCount}>点击增加</button>
{/*要切记,react中绑定事件必须是回调函数,千万不要在绑定事件时调用,否则会造成死循环*/}
<button onClick={()=>{this.subCount()}}>点击减少</button>
</>
)
}
}
}
7.表单绑定
非受控组件
:不需要绑定任何数据的组件叫做非受控组件,反之则是受控组件。
如果表单的某个属性,比如value与state中绑定,那么就叫做受控组件,而我们下面给绑定事件,再赋值,这个不叫受控组件。
class App extends Component<any,isState>{
state = {
firstName:''
}
chanHandler = (e:any)=>{
this.setState({
[e.target.value]:e.target.value
})
}
render(){
return(
<>
<h2>{this.state.firstName}</h2>
<input type="text" name="name" onChange={this.changeHandler} />
</>
)
}
}
不同的表单控件可能略有不同。
8.组件传值
父子组件传值
父子组件传值借助于props,但是子组件不能直接修改传递过来的props。在函数组件
中,可以直接使用形参接收props
,在类组件
中,直接拿到props,进行调用即可 ,在工作中可能解构使用的较多{name}:{name:string}
const Son = (props:any)=>{
return (
<>
<h2>我是子组件,我接收到的值是{props.name}</h2>
</>
)
}
class Father entends Conponent<any,any>{
render(){
return(
<>
<Son name="zs"/>
</>
)
}
}
设置默认值:如果说组件没有传递props,则可以设置默认值。对于函数组件,Son.defaultProps
。对于类组件则直接static defaultProps = { }
属性验证:可以借助第三方插件库 prop-types
,不过对于我们使用typescript来说,可以不用。
插槽
父向子传递时,可以在调用内部传递JSX内容。子组件则可以直接使用props.children
来接收传递过来的数据。
在React中没有具名插槽,作为一个数组,按顺序获取即可
子传父
与vue中类似,调用父组件传递过来的自定义事件。子组件在调用父组件的自定义方法时,直接this.props.MyFn(arg)
即可,在父组件中的函数参数则是子组件传递过来的值。
状态提升
在React中,将多个组件中需要共享的state向上移动到它们的最近公共父组件中,便可以实现共享state,这就是所谓的『整体提升』
将公共的属性放在父组件中,父组件将这个响应式数据分别通过props传递到子组件中,同时传递给子组件一个修改该数据的方法,之后子组件就可以进行操作。
上下文Context实现跨组件通信
当父级嵌套多层子级时,需要从祖先组件传递到孙组件甚至嵌套更多层时,我们使用props就需要多层传递,非常的繁琐。因此我们可以使用上下文Context
,类似于vue中的Provide
和Inject
,使用方法类似,并无太大差别。
本质上来讲,Context 是数据提供者和数据消费者的概念
// src/App.tsx import React, { Component } from 'react'; interface isState { } // 第一步:创建上下文对象,并设置默认值 const MyContext = React.createContext("默认值"); /* 第三步: 方案一,如果后代组件是类组件 可以通过 类的静态属性 static contextType = 上下文对象 获取祖先组件的值, 然后在jsx中 通过 this.context 取值即可,如果使用 TS 需要进行声明 方案二,如果不使用 静态属性,那么可以 通过 上下文对象.Consumer 来获取数据, 在其内部图片通过函数返回jsx代码,函数的默认参数就是祖先组件传递的值 */ class Third extends Component<any, isState>{ // 方案一:直接拿到直接使用 static contextType = MyContext // 在 TS 版本中使用需要注意 context!: React.ContextType<typeof MyContext> render() { return ( <div> <h3>third!!</h3> <i>{this.context}</i> <div> {/* 方案二:在需要消费数据的地方包裹内容,直接消费即可 */} <MyContext.Consumer > { (value) => { return <mark>{value}</mark> } } </MyContext.Consumer> </div> </div> ) } } const Second = () => { return ( <div> <h2>second</h2> <Third /> </div> ) } const First = () => { return ( <div> <h1>first</h1> <Second /> </div> ) } class App extends Component<any, isState> { render() { return ( <div> {/* 第二步:在需要传值的地方,通过 上下文 对象 的 Provider 组件以及value属性配合传递数据 */} <MyContext.Provider value="传家宝"> <First /> </MyContext.Provider> </div> ); } } export default App
9.高阶组件
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。
HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
具体而言,高阶组件是参数为组件,返回值为新组件的函数。Higher - Order Components,其实就是一个函数,传给它一个组件,它返回一个新的组件。形如:
const NewComponent = HOC(YourComponent)
通俗的来讲,高阶组件
就相当于手机壳,通过包装组件,增强组件功能。
代码示例:
import React, { Component } from 'react';
interface isState { }
localStorage.setItem('userName', '孙悟空')
// 1.高阶组件:就是一个纯函数,将一个组件作为参数,并且返回另一个新的组件
// 2.高阶组件内部可以实现 可以复用的业务逻辑
// 3.高阶组件内可以通过 类似于 父子组件关系 传递数据
// 4.<></>表示react的空标签,同理 react 常用的另一个空标签 React.Fragment
const withFooter = (OldCom: any) => {
return class extends Component {
state = { userName: '' }
componentDidMount() {
console.log(localStorage.getItem('userName'))
this.setState({
userName: localStorage.getItem('userName')
})
}
render() {
return (
<React.Fragment>
<OldCom userName={this.state.userName} />
<footer>
前端 so Easy!
</footer>
</React.Fragment>
)
}
}
}
class Child1 extends Component<any, isState> {
render() {
return (
<div>
<h1>子组件 1 - {this.props.userName}</h1>
</div>
)
}
}
const ChildA = withFooter(Child1)
class Child2 extends Component<any, isState> {
render() {
return (
<div>
<h1>child2-{this.props.userName}</h1>
</div>
)
}
}
const ChildB = withFooter(Child2)
class App extends Component {
render() {
return (
<div>
<ChildA />
<hr />
<ChildB />
</div>
);
}
}
export default App;
10.React中ref的使用
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
1、使用字符串 ref 获取 DOM
直接使用字符串方式获取 ref 节点已经被废弃,但是还能使用,不推荐使用
import React, { Component } from 'react'; interface isState { } class App extends Component<any, isState> { componentDidMount(): void { // 使用字符串的方式获取 ref DOM 节点,目前已经被废弃 console.log(this.refs.title); } render() { return ( <div> <h2 ref="title">我是一个标题</h2> </div> ); } } export default App;
2、使用 createRef 直接获取 DOM
import { Component, createRef } from 'react'; interface isState { } class App extends Component<any, isState> { // 使用 createRef 方式获取 ref DOM 节点,推荐玩法 titleRef = createRef<any>() componentDidMount(): void { console.log(this.titleRef); } render() { return ( <div> <h2 ref={this.titleRef}>我是一个标题</h2> </div> ); } } export default App;
3、使用 ref 获取当前组件并直接操作
import { Component, createRef} from 'react'; interface isState { } class ChildA extends Component<any, isState> { state = { count: 10 } clickHandler = () => { this.setState({ count: this.state.count + 1 }) } render() { return ( <> <h1>childA - {this.state.count}</h1> </> ) } } // 1.在严格模式下,不要使用 字符串类型的ref,它已被废弃 // 2.如果在类组件中使用ref,建议使用 createRef // 3.通过 createRef 创建的 ref 的 current值可以获取到子组件的实例(前提条件是子组件为类组件) class App extends Component<any, isState>{ childARef = createRef<any>() divRef = createRef<any>() // 操作 child1,直接调用其组件内部的方法 add = () => { console.log(this.childARef.current.state) this.childARef.current.clickHandler() } // 在父组件中获取子组件实例并操作 componentDidMount() { console.log(this.childARef) console.log(this.divRef.current.innerHTML) } render() { return ( <div> <button onClick={this.add}>加</button> <ChildA ref={this.childARef} /> {/* ref也可以用在DOM */} <div ref={this.divRef}>ref-DOM</div> </div> ); } } export default App;
4、使用 ref 转发子组件
Ref 转发是一个可选特性,其允许某些组件接收
ref
,并将其向下传递(换句话说,“转发”它)给子组件。import { Component, createRef, forwardRef } from 'react'; interface isState { } // 使用 forwardRef 高阶组件转发ref,需要接收第二个参数 ref 就代表当前转发的对象 const ChildB = forwardRef((props, ref: any) => { return ( <> <h1 ref={ref}>ChildB 组件</h1> </> ) }) class ChildA extends Component<any, isState> { state = { count: 10 } clickHandler = () => { this.setState({ count: this.state.count + 1 }) } render() { return ( <> <h1>childA - {this.state.count}</h1> </> ) } } // 1.在严格模式下,不要使用 字符串类型的ref,它已被废弃 // 2.如果在类组件中使用ref,建议使用 createRef // 3.通过 createRef 创建的 ref 的 current值可以获取到子组件的实例(前提条件是子组件为类组件) class App extends Component<any, isState>{ childARef = createRef<any>() childBRef = createRef<any>() divRef = createRef<any>() // 操作 child1,直接调用其组件内部的方法 add = () => { console.log(this.childARef.current.state) this.childARef.current.clickHandler() } // 在父组件中获取子组件实例并操作 componentDidMount() { // 如果子组件是函数式组件,调用子组件的时候,添加的ref属性 需要在 定义子组件时通过 React.forwardRef 高阶组件包裹, // 第二个参数就是传递的ref,这样就可以在父组件中获取到子组件中的 DOM 标签,获取组件内部的 DOM 标签玩法 console.log(this.childARef) console.log(this.childBRef) } render() { return ( <div> <button onClick={this.add}>加</button> <ChildA ref={this.childARef} /> <ChildB ref={this.childBRef} /> {/* ref也可以用在DOM */} <div ref={this.divRef}>ref-DOM</div> </div> ); } } export default App;
5、使用 ref 注意事项
当
ref
属性用于 HTML 元素时,使用React.createRef()
创建的ref
接收底层 DOM 元素作为其current
属性。当
ref
属性用于自定义 class 组件时,ref
对象接收组件的挂载实例作为其current
属性。你不能在函数组件上使用
ref
属性,因为他们没有实例。如果非要使用,实际上是转发ref(父组件中获取到了子组件的某个DOM)https://react.docschina.org/docs/hooks-reference.html#usedebugvalue)
11.Hooks
hooks只适用于函数组件,使用hooks则没有类组件
useState响应式对象
在类组件中使用state,在Hooks中则使用useState
,会返回一个数组,可以使用数组解构。修改count的状态是一个异步依赖采集操作,也就是多个同样的会被覆盖。如果想要不被覆盖,可以使用函数柯里化的形式。
const [count,setCount] = useState(10)
// setCount是用来修改count的函数
const add = ()=>{
setCount(count+1)
}
useEffect副作用
在hooks中,只能使用副作用来模拟不同的生命周期
在类组件中,我们使用componentDidMount
生命周期来处理当组件挂载之后的逻辑。在Hooks中,则可以借助useEffect来模拟。需要注意的是,useEffect需要传递第二个参数才相当于componentDidUpdate
,否则就相当于update。在第一个参数结束时,return一个函数对象,那么这个函数对象中就可以处理组件销毁前的逻辑,类似于componentWillUnmount
总的来说,useEffect可以模拟三个生命周期,componentDidMount和componentDidUpdate。如果只需要在页面初始化调用一次,则useEffect需要传递第二个参数,为空数组,如果监听更新事件,也就是监听所有,则不需要传递第二个参数。同样的,当在第一个参数结束时返回一个函数对象,则这个函数对象中就可以处理组件销毁前的逻辑。
useEffect生命周期的模拟
- 不传第一个参数,监听的是所有的响应式对象,类似于
comonentDidUpdate
- 传第二个参数是空数组[],则只监听初始化渲染之后的操作,类似于
componentDidMount
- 传第二个参数[postion],也就是之间听position的变化之后的操作,类似于
watch
- 在副作用中,return一个函数对象,其中就可以写我们卸载页面之前的逻辑,类似于
componentWillUnmount
useRef(或者Dom或组件)
使用useRef就可以获取当前的Dom元素或者是当前的组件
如果想要获取函数组件的ref,则必须要转发
// 函数式组件的 ref 转发,高阶组件思想
const Child = forwardRef((props, ref: any) => {
return <div ref={ref}>child</div>
})
useReducer 管理数据
useReducer 也叫做状态管理,类似简易的 Vuex 来管理数据,使用步骤:
- 创建初始值
initialState
- 创建所有操作
reducer(state, action)
- 传给
userReducer
,得到读和写API- 调用写
({type: '操作类型'})
- 总的来说,
useReducer
是useState
的复杂版。import { useEffect, useReducer } from "react" // 1.创建初始化数据 const initalState = { count: 10, proList: [] } // 2.设置reducer ---- 唯一改变数据方式就是通过reducer实现 // action { type, payload } const reducer = (state: any, action: any) => { switch (action.type) { case 'ADD': return Object.assign({}, state, { count: state.count + action.payload }) case 'CHANGE_PRO_LIST': return Object.assign({}, state, { proList: action.payload }) default: return state } } // 有了useReducer,不需要useState, 但是企业项目一般不去这么使用 // 因为,如果遇到多个组件需要共享状态时,单纯useReducer就显得无能为力 const App = () => { const [state, dispatch] = useReducer(reducer, initalState) useEffect(() => { fetch('http://121.89.205.189:3001/api/pro/list').then(res => res.json()).then(res => { dispatch({ type: 'CHANGE_PRO_LIST', payload: res.data }) }) }, []) return ( <div> {state.count} <button onClick={() => { dispatch({ type: 'ADD', payload: 5 }) }}>加5</button> <ul> { state.proList && state.proList.map((item: { proid: any; proname: any; }) => ( <li key={item.proid}>{item.proname}</li> )) } </ul> </div> ); }; export default App;
useContext 消费数据
使用
C = createContext(initial)
创建上下文使用
<C.Provider>
圈定作用域在作用域内使用
useContext(C)
来使用上下文import { createContext, useContext } from 'react'; // 1. 创建数据供应 const MyContext = createContext('MyContext') const ColorContext = createContext('ColorContext') const Child1 = () => { // 3. 消费数据 const my = useContext(MyContext) const color = useContext(ColorContext) return ( <div> {my} - {color} </div> ) } const Child2 = () => { // 3. 消费数据 const my = useContext(MyContext) const color = useContext(ColorContext) return ( <div> {color} - {my} </div> ) } const App = () => { return ( <div> {/* 2. 供应数据 */} <MyContext.Provider value="传家宝"> <ColorContext.Provider value="red"> <Child1 /> <Child2 /> </ColorContext.Provider> </MyContext.Provider> </div> ); } export default App;
useMemo(类似computed)
useMemo
类似于vue中的computed,使用方式也很简单,可以对指定的响应式对象进行监听,要有返回值。useMemo和useEffect有差异,前者必须返回一个值,后者则是对数据变化的副作用进行监控。
import {useState} from 'react' const App = ()=>{ const [countA,setCountA] = useState(0) const [countB,setCountB] = useState(0) //第二个参数数组中是需要依赖的响应式数据 const computeCount = useMemo(()=>{ return countA + countB },[countA,countB]) return ( <> <div>counta 的值是:{counta}</div> <div>countb 的值是:{countb}</div> <Child value={cachedValue} /> <button onClick={() => setCounta(v => v + 1)}>countA + 1</button> <button onClick={() => setCountb(v => v + 1)}>countB + 1</button> </> ) }
memo(高阶函数)
只有依赖的props数据发生变化,才会触发子组件的更新,明显的提高了性能。
比如当我们在使用useMomo进行计算属性时,再将countA的值传递给子组件Child。如果Child是个普通组件,那么每次当countA或者countB的数据发生变化时,父组件就会重新进行diff比对,重新进行渲染,也就是再次调用子组件,我们在子组件中只接受了countA数据。我们只希望当countA的数据变化时才会再次更新Child子组件,此时就可以使用memo
。 memo是高阶函数的概念,也就是将我们的Child组件通过memo函数进行包裹,那么只有当其中依赖的数据发生变化时,Child组件才会再次进行渲染,明显的提高了性能。
import { useMemo, useState, memo } from "react"; interface IProps { value: number } // 我们发现,不管是 counta、counta 任意发生变化都会触发子组件重新更新,明显消耗性能 // let Child = (props: IProps) => { // console.log("child 子组件被重新渲染了") // return (<div>父组件传递过来的值是:{props.value}</div>) // } // memo 是高阶组件概念,返回一个组件即为高阶组件 // memo的作用,只有依赖的数据发生变化时,才会触发子组件重新更新,明显提高性能 // 这里依赖的是 counta,而 countb 变化并不会触发子组件更新,如果都不依赖,则都不会更新 let Child = memo((props: IProps) => { console.log("child 子组件被重新渲染了") return (<div>父组件传递过来的值是:{props.value}</div>) }) export default function Home() { const [counta, setCounta] = useState(0); const [countb, setCountb] = useState(0); // useMemo 和 useEffect 的使用基本上一样,监听数据的变化,触发新的返回值 const cachedValue = useMemo(function () { return counta + 1 }, [counta]) return ( <> <div>counta 的值是:{counta}</div> <div>countb 的值是:{countb}</div> <Child value={cachedValue} /> <button onClick={() => setCounta(v => v + 1)}>countA + 1</button> <button onClick={() => setCountb(v => v + 1)}>countB + 1</button> </> ); }
pureComponent
pureComponent就类似于函数组件中的memo使用。
手机品牌管理案例
import { useState, useMemo } from 'react'
import './MyPhone.css'
const MyPhone = () => {
// 手机列表
const [phoneList, setPhoneList] = useState([
{
name: '华为',
date: new Date().toString(),
},
{
name: '小米',
date: new Date().toString(),
},
{
name: '苹果',
date: new Date().toString(),
},
{
name: 'vivo',
date: new Date().toString(),
},
])
// 弹窗显示
const [modelShowTag, setModelShowTag] = useState(false)
//添加手机名称输入
const [inputPhone, setInputPhone] = useState('')
// 查询手机输入
const [searchInput, setSearchInput] = useState('')
// 点击添加按钮显示弹窗
const openModel = () => {
setModelShowTag(true)
}
// 删除按钮
const removePhone = (name: string) => {
// alert('当前被删除的id' + name)
setPhoneList(phoneList.filter((item) => item.name !== name))
}
// 点击弹窗中的取消按钮
const closeModel = () => {
setModelShowTag(false)
}
// 回车事件
const enter = (e: any) => {
// console.log(e)
if (e.code === 'Enter') {
setPhoneList([
{
name: inputPhone,
date: new Date().toString(),
},
...phoneList,
])
setInputPhone('')
setModelShowTag(false)
}
}
// 点击弹窗中的确认按钮
const addPhone = () => {
// 输入的内容不能为空
if (inputPhone === '') {
alert('输入不能为空')
return
}
setPhoneList([
{
name: inputPhone,
date: new Date().toString(),
},
...phoneList,
])
setInputPhone('')
setModelShowTag(false)
}
// 类似于计算属性,来实现搜索,useMemo()
const computedPhoneList = useMemo(() => {
return phoneList.filter((item) => item.name.includes(searchInput))
}, [phoneList, searchInput])
return (
<>
{/* 这里是顶部显示的内容 */}
<div className="header_con">
<div className="header">
<h1>手机品牌管理</h1>
<input
type="text"
placeholder="请输入搜索条件"
value={searchInput}
onChange={(e) => {
setSearchInput(e.target.value)
}}
/>
</div>
</div>
{/* 显示内容的标题部分 */}
<div className="tb_title">
<h3>品牌列表</h3>
<button onClick={openModel}>新增品牌</button>
</div>
{/* 显示手机列表信息 */}
<table className="tb">
<thead>
<tr>
<th>编号</th>
<th>品牌名称</th>
<th>创立时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{computedPhoneList.map((item, index) => {
return (
<tr key={'phone' + index}>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>
<button onClick={() => removePhone(item.name)}>删除</button>
</td>
</tr>
)
})}
{computedPhoneList.length <= 0 && (
<tr>
<td colSpan={4}>没有品牌数据</td>
</tr>
)}
</tbody>
</table>
{/* 新增品牌弹框 */}
{modelShowTag && (
<div className="add_con">
<div className="add">
<h3>新增品牌</h3>
<div className="add_form">
<label>品牌名称:</label>
<input
type="text"
value={inputPhone}
onChange={(e) => {
setInputPhone(e.target.value)
}}
onKeyDown={(e) => {
enter(e)
}}
/>
</div>
<div className="btns">
<input type="button" value="取消" onClick={closeModel} />
<input type="button" value="添加" onClick={addPhone} />
</div>
</div>
<div className="mask"></div>
</div>
)}
</>
)
}
export default MyPhone
12、使用自定义 hooks
区别于自定义组件,自定义 hooks 的目的是是可以 逻辑复用,而组件是模块复用
- 自定义 Hooks 是一个函数,约定函数名称必须以 use 开头,React 就是通过函数名称是否以 use 开头来判断是不是 Hooks
- Hooks 只能在函数组件中或其他自定义 Hooks 中使用,否则,会报错!
- 自定义 Hooks 用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值(就像使用普通函数一样)
当我们整个页面需要获取用户鼠标移动的坐标时,不使用hook的代码,我们可以这样写
import { useState, useEffect } from 'react' export default function App() { const [position, setPosition] = useState({ x: 0, y: 0 }) useEffect(() => { const move = (e:any) => { setPosition({ x: e.x, y: e.y }) } document.addEventListener('mousemove', move) return () => { document.removeEventListener('mousemove', move) } }, []) return ( <div> x 坐标:{position.x} --- y 坐标:{position.y} </div> ) }
- 当我们页面中有一个图片要跟随鼠标移动时,不使用hook的代码,我们也可以这样写:
import { useState, useEffect } from 'react' export default function App() { const [position, setPosition] = useState({ x: 0, y: 0 }) useEffect(() => { const move = (e: any) => { setPosition({ x: e.x, y: e.y }) } document.addEventListener('mousemove', move) return () => { document.removeEventListener('mousemove', move) } }, []) return ( <div> <img src="https://img0.baidu.com/it/u=1672615241,1947096361&fm=253&fmt=auto&app=138&f=PNG?w=192&h=192" style={{ position: 'absolute', top: position.y - 50, left: position.x - 50, }} alt="PIC" /> </div> ) }
- 很明显,以上两个例子呈现效果不同,但使用的逻辑代码大部分相同时,这些逻辑代码我们就可以使用自定义 hook 进行逻辑复用,提取以上两个例子里可以复用的逻辑代码,新建一个名为 useMousePosition 的文件
// src/hooks/useMousePosition.ts import { useState, useEffect } from 'react' export default function useMousePosition() { const [position, setPosition] = useState({ x: 0, y: 0 }) useEffect(() => { const move = (e: any) => { setPosition({ x: e.x, y: e.y }) } document.addEventListener('mousemove', move) return () => { document.removeEventListener('mousemove', move) } }, []) return position }
- 我们在 useMousePosition 函数中提取了此功能,现在,我们可以将其导入到要使用的任何位置!最后像使用普通函数那样使用即可
import useMousePosition from "./hooks/useMousePosition"; export default function App() { const position = useMousePosition() return ( <div> <img src="https://img0.baidu.com/it/u=1672615241,1947096361&fm=253&fmt=auto&app=138&f=PNG?w=192&h=192" style={{ position: 'absolute', top: position.y - 50, left: position.x - 50, }} alt="PIC" /> </div> ) }
13.react-router 路由
使用之前先安装对应的包,react-router-dom
声明式导航
-
使用前先需要使用HashRouter或者historyRouter对App组件进行包裹
import { HashRouter } from 'react-router-dom' import App from './App' const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) root.render( <React.StrictMode> <HashRouter> <App></App> </HashRouter> </React.StrictMode> )
-
在需要使用路由的地方进行引入,声明式导航类似于我们的
a
标签,也就说点击之后会跳转到不同的路由。在需要使用的地方先导入对应的路由以及组件{/* 第一步,引入组件以及路由 */} import { Home, Me, Index, About, List } from './pages/index' import { Route, Routes, Link } from 'react-router-dom' const App = () => { return ( <> {/* 第三步: 声明导航,类似于a标签,要和底下的路由出口匹配 */} <nav> <Link to="/index">首页</Link> <Link to="/list">列表</Link> <Link to="/home">主页</Link> <Link to="/about">关于</Link> <Link to="/me">我的</Link> </nav> <h1 style={{ color: 'red' }}>我是公共父组件</h1> {/*第二步: 声明路由出口 */} <Routes> <Route path="/home" element={<Home></Home>}></Route> <Route path="/about" element={<About></About>}></Route> <Route path="/index" element={<Index></Index>}></Route> <Route path="/me" element={<Me></Me>}></Route> <Route path="/list" element={<List></List>}></Route> </Routes> </> ) } export default App
编程式导航
编程式导航一般就是点击某个按钮,让其跳转到指定页面,可以传递参数。
使用useNavigate()
来进行跳转,尽量不要在跟组件中使用编程式导航。
-
引入
import {useNavigate} from 'react-router-dom'
-
创建navigate对象
const navigate = useNavigate()
-
在需要使用的地方进行调用,比如跳转到home路由,则
navigate('/home')
-
其中
navigate(-1)
是回退,也就是页面goback,navigate(1)
是向前 -
可以接受第二个参数,replace表示之前组件的状态是否保存,state是要传递的数据
navigate('/home',{ //其中replace为true则说明是清除之前的所有组件信息,false则保留 replate:false, //state则是要路由要传递的数据 state:{ title:'标题', data:'fd' } })
路由传参
路由参数:在Route定义渲染组件时给定动态绑定的参数。
React路由传参方式有三种:
动态路由(param)
- 在 Route 中声明路由
/film/detail/:id
<Route path='details/:type' element={<Details />} />
- 以
/film/detail/888
形式传递的数据
<Link to='/details/telphone'>手机</Link>
<Link to='/details/computer'>电脑</Link>
- 在目标页面落地组件中取 params 时需要借助 useParams()
import { useParams } from 'react-router-dom'
const params = useParams()
查询字符串(query)
- 通过地址栏中的
?key=value&key=value
传递,不需要声明路由规则
<Link to='/details?type=telphone'>手机</Link>
<Link to='/details?type=computer'>电脑</Link>
- 在取 search时需要借助 useSearchParams()
import { useSearchParams } from 'react-router-dom'
const [search]=useSearchParams()
const type = search.get("type")
隐式传参(state),通过地址栏是观察不到的
- 不合适写在声明式导航中,写在编程式导航中更加合适
const navigate = useNavigate();
const goBack = () => {
navigate(-1)
}
const goHome = () => {
navigate('/home', {
replace: false,
state: {
id: 8888,
title: "state 传递标题",
content: "state 传递内容"
}
})
}
- 在落地组件中取 state,借助 useLocation(),刷新页面后数据依然可以存在
import { useLocation } from 'react-router-dom'
const location= useLocation()
const {id,title,content} = location.state
// 首页内容
export const Home = () => {
const location = useLocation()
// 需要判断是否传递参数,若未传递则进行兼容判断
const state = location.state ? location.state : ''
return (
<section className="home">
<h1>公司首页</h1>
<p>首页内容,state 传递的数据是:{state && state.id} {state && state.title} {state && state.content}</p>
</section>
)
}
嵌套路由的使用
-
在有一些功能中,往往请求地址的前缀是相同的,不同的只是后面一部份,此时就可以使用多级路由(路由嵌套)来实现此路由的定义实现。
例如,它们路由前缀的 home 是相同的,不同的只是后面一部份
http://localhost:3000/#/home/history
http://localhost:3000/#/home/persons
- 嵌套路由,直接嵌套在 父页面的路由内部
<Route path='/home' element={<Home />}>
<Route path='history' element={<History />} />
<Route path='persons' element={<Persons />} />
</Route>
- 可以定义索引路由,配置默认显示页面
<Route path='/home' element={<Home />}>
{/* 索引路由 */}
<Route index element={<History />} />
<Route path='history' element={<History />} />
<Route path='persons' element={<Persons />} />
</Route>
- 在落地页面中使用 Link 进行路由选择
- 在落地页面中使用
作为路由出口(类似于 Vue 的 router-view)
// src/pages/mypages.tsx
import { Link, Outlet, useLocation } from "react-router-dom";
// 首页内容
export const Home = () => {
const location = useLocation()
// 需要判断是否传递参数,若未传递则进行兼容判断
const state = location.state ? location.state : ''
return (
<section className="home">
<h1>公司首页</h1>
<p>首页内容,state 传递的数据是:{state && state.id} {state && state.title} {state && state.content}</p>
<nav>
{/* 添加了四个导航组件Link */}
<Link to='/home/history'>公司历史</Link>
<Link to='/home/persons'>公司团队</Link>
</nav>
<Outlet />
</section>
)
}
重定向与404路由
- 重定向路由,例如首页的时候 不输入完整路由 /home 输入 / 即可访问首页
<Route path="/" element={<Navigate to="/home" />}></Route>
// 官方没有 redirect 组件,需要自定义 Redirect 组件
<Route path="*" element={<Redirect to="/film"/>}/>
function Redirect({to}){
const navigate =useNavigate()
useEffect(() => {
navigate(to,{replace:true})
})
return null
}
- 项目中少不了404页面的配置,在React里面配置404页面需要注意,在404路由的位置,不需要给定具体的路由匹配规则,不给
path
表示匹配*
,即所有的路由都会匹配
// 404 页面
export const NotFound404 = () => (
<div className="whoops-404">
<h1>没有页面可以匹配</h1>
</div>
)
// src/App.tsx
import { Routes, Route, Link, Navigate } from "react-router-dom";
import { Home, History, Persons, About, Contact, Products, Details, Events, NotFound404 } from "./pages/mypages";
import "./app.scss"
const App = () => {
return (
<div>
{/* 页面公共部分 */}
<h2>路由学习使用 - 公共部分 - 每个页面都有</h2>
{/* 添加了四个声明式路由导航组件Link */}
<nav>
<Link to='/home'>公司首页</Link>
<Link to='/about'>关于我们</Link>
<Link to='/events'>企业事件</Link>
<Link to='/products'>公司产品</Link>
<Link to='/contact'>联系我们</Link>
</nav>
{/* 路由匹配渲染出口 */}
<Routes>
<Route path="/" element={<Navigate to="/home" />}></Route>
<Route path='/home' element={<Home />}>
{/* 索引路由 */}
<Route index element={<History />} />
<Route path='history' element={<History />} />
<Route path='persons' element={<Persons />} />
</Route>
<Route path='/about' element={<About />} />
<Route path='/contact' element={<Contact />} />
<Route path='/products' element={<Products />} />
<Route path='details/:type' element={<Details />} />
<Route path='/events' element={<Events />} />
<Route path='*' element={<NotFound404 />}></Route>
</Routes>
</div>
);
};
export default App;
路由拦截
在 React 中路由拦截,需要使用到高阶组件的概念,比较灵活
<Route path="/center" element={
<AuthComponent>
<Center></Center>
</AuthComponent>
}/>
function AuthComponent({children}){
return localStorage.getItem("token")? children : <Redirect to="/login"/>
}