2023年最全前端React18面试题考点

news/2024/11/23 2:44:03/

目录

React18

特点

声明式编码

单向数据流 

组件化

虚拟DOM(Virtual Dom)(同Vue)

Diff算法(同Vue)

组件属性

props

state

refs

总结

受控组件和非受控组件

事件event

事件处理的几种方法

事件中this的处理

事件传参处理

鼠标事件 mouseenter与mouseover区别

跨组件通信

生命周期

状态提升

复用组件

Render Props模式

HOC高阶组件模式

Hooks

useState

useReducer

useEffect

useRef

useCallback和useMemo

自定义Hook

StrictMode严格模式

Router

路由模式(同Vue)

基础路由搭建

重定向路由

自定义全局守卫

动态路由

Redux状态管理库

应用

React脚手架

Angular,Vue,React对比

Angular

React和Vue

MVC、MVP、MVVM模式

MVC (Model View Controller)

MVP(Model View Presenter)

MVVM (Model View View Model)


React18

React是用来构造用户界面的JS库 

在React18中,需要使用两个文件来初始化框架:

  • react.development.js 或 react模块 -> 生成虚拟DOM

  • react-dom.development.js 或 react-dom/client模块 -> Diff算法 + 处理真实DOM

下面就是初始化React程序的代码。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="../react.development.js"></script><script src="../react-dom.development.js"></script>
</head>
<body><div id="app"></div><script>// React对象 -> react.development.js(产生虚拟DOM)// ReactDOM对象 -> react-dom.development.js(渲染成真实DOM)//原生DOM获取let app = document.querySelector('#app');// root根对象,渲染react的DOM,React18let root = ReactDOM.createRoot(app); // React.createElement() -> 创建虚拟DOM //createElement(标签,属性,内容)let element = React.createElement('h2', {title: 'hi'}, 'hello world');root.render(element);</script>
</body>
</html>

特点

虚拟DOM,组件化设计模式,声明式代码,单向数据流,使用jsx描述信息

声明式编码

  • 命令式编程:流程(每一步指令怎么去做)
  • 声明式编码:目标,而非流程

因没有指令的概念,所以条件渲染和列表渲染都要通过命令式编程来实现(即JS本身的能力)

map()方法
let app = document.querySelector('#app');
let root = ReactDOM.createRoot(app); 
let data = [{ id: 1, text: 'aaa' },{ id: 2, text: 'bbb' },{ id: 3, text: 'ccc' }
];
let element = (<ul>{data.map(v=><li key={v.id}>{v.text}</li>)}</ul>
);
root.render(element);

单向数据流 

数据主要从父节点传到子节点(通过props),如果父级的某个props改变了,React会重新渲染所有子节点

组件化

可复用的代码可以抽成组件共同使用(UI,方法等) 

每个UI块都是一个组件,每个组件都有一个状态。

虚拟DOM(Virtual Dom)(同Vue)

虚拟 DOM,根据模板生成一个js对象(使用createElement,方法),取代真实的 DOM 。

当页面打开时浏览器会解析 HTML 元素,构建一颗 DOM 树,将状态全部保存起来

Vue和React框架都会自动控制DOM的更新,而直接操作真实DOM是非常耗性能的,所以才有了虚拟DOM的概念

React遵循可观察的模式,并监听状态变化。当组件的状态改变时,React更新虚拟DOM树。

缺点首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢
 

Diff算法(同Vue)

通过同层的树节点进行比较的高效算法,比较方式:diff整体策略为:深度优先,同层比较

总的来说就是减少DOM,重绘回流


react生成的新虚拟DOM和旧虚拟DOM的比较规则:

  • 如果旧的虚拟DOM中找到了与新虚拟DOM相同的key:

如果内容没有变化,就直接只用之前旧的真实DOM
如果内容发生了变化,就生成新的真实DOM

  • 如果旧的虚拟DOM中没有找到与新虚拟DOM相同的key:

根据数据创建新的真实的DOM,随后渲染到页面上

组件属性

每个 React 组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示。

props

对外公开属性,只读

传递数据

<body><div id = "div"></div></body>
<script type="text/babel">
// 函数组件
function Welcome(props){return (<div>hello world, {props.msg}</div>);  
}
let element = (<Welcome msg="hi react" />
);// 类组件class Person extends React.Component{render(){return (<ul>//接受数据并显示<li>{this.props.name}</li><li>{this.props.age}</li><li>{this.props.sex}</li></ul>)}}//等同const p = {name:"张三",age:"18",sex:"女"}ReactDOM.render(<Person {...p}/>,document.getElementById("div"));//传递数据ReactDOM.render(<Person name="tom" age = "41" sex="男"/>,document.getElementById("div"));
</script>

传递函数

// 子组件
class Head extends React.Component {render(){this.props.getData('子组件的问候~~~')return (<div>Head Component</div>);}
}
// 父组件
class Welcome extends React.Component {getData = (data) => {console.log(data)}render(){return (<div>hello world, {this.props.msg}<br /><Head getData={this.getData} /></div>);}
}

构造函数获取props

class Foo {constructor(props){this.props = props;}
}
class Bar extends Foo {constructor(props){super(props);console.log(this.props);}render(){console.log(this.props);return '';}
}
let props = {msg: 'hello world'
};
let b = new Bar(props);
b.props = props;
b.render();

多属性传递props

class Welcome extends React.Component {render(){//解构let { msg, username, age } = this.props;console.log( isChecked );return (<div>hello world, {msg}, {username}, {age}</div>);}
}
let info = {msg: 'hi react',username: 'xiaoming',age: 20
};
let element = (<Welcome {...info} />
);

设置props初始值和类型

import PropTypes from 'prop-types'
class Welcome extends React.Component {static defaultProps = {age: 0}static propTypes = {age: PropTypes.number}...
}

state

组件的私有属性,值是对象(可以包含多个key-value的组合)

通过state的变化设置响应式视图受控于当前组件

class Welcome extends React.Component {state = {msg: 'hello',count: 0}handleClick = () => {   this.setState({msg: 'hi'});}render(){console.log('render');return (<div><button onClick={this.handleClick}>点击</button>{this.state.msg}, {this.state.count}</div>);}
}
let element = (<Welcome />
);

refs

React操作原生DOM跟Vue框架是类似的,都是通过ref属性来完成的

class Welcome extends React.Component {myRef = React.createRef()handleClick = () => {   //console.log(this.myRef.current);  // 原生DOM inputthis.myRef.current.focus();//获取焦点}render(){return (<div><button onClick={this.handleClick}>点击</button><input type="text" ref={this.myRef} /></div>);}
}

总结

props


公开,单向数据流值,父子组件间的唯一通信不可改

1.每个组件对象都会有props(properties的简写)属性

2.组件标签的所有属性都保存在props中

3.内部读取某个属性值:this.props.propertyName

state


私有(通过Ajax获取回来的数据,一般都是私有数据),

React 把组件看成是一个状态机(State Machines),

只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。


refs


当需要获取某一个真实的DOM元素来交互,比如文本框的聚焦、触发强制动画等

当需要操作的元素和获取的元素是同一个时,无需ref

受控组件和非受控组件

非受控组件
现用现取,官方建议尽量少用ref,用多了有一定的效率影响

handleSubmit = (event) => {event.preventDefault() //阻止表单提交const {username, password} = this//拿到的是form下的username, password结点alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)}render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>


受控组件
将输入维护到state,等需要时再从state取出来

    class Login extends React.Component {//state最好要初始化状态state = {username: '', //用户名password: '' //密码}//保存用户名到状态中saveUsername = (event) => {
this.setState({username: event.target.value})}//保存密码到状态中savePassword = (event) => {
this.setState({password: event.target.value})}//表单提交的回调handleSubmit = (event) => {event.preventDefault() //阻止表单提交const {username, password} = this.state//获得的是state下的username,password值alert(`你输入的用户名是:${username},你输入的密码是:${password}`)}

事件event

React中的事件都是采用事件委托的形式,所有的事件都挂载组件容器上,其次event对象是合成处理过的

事件处理的几种方法

import React from 'react'
class Test extends React.Component{handleClick2(){console.log('click2')}hangleClick4 = () =>{console.log('click4')}render(){return(<button onClick={ console.log('click1')}>click1</button><button onClick={ this.handleClick2.bind(this)}>click2</button><button onClick={ () => {console.log('click3')}>click3</button><button onClick={ this.hangleClick4 }>click3</button>)}
}
export default Test

事件中this的处理

class Welcome extends React.Component {handleClick = (ev) => {  //推荐 public class fields语法,箭头函数不会创建自己的                                      this,它只会从自己的作用域链的上一层继承 this。console.log(this);   //对象console.log(ev);}handleClick(){           //不推荐 要注意修正指向console.log(this);   //按钮 }render(){return (<div><button onClick={this.handleClick}>点击</button>hello world</div>);}
}
let element = (<Welcome />
);

事件传参处理


推荐采用函数的高阶方式,具体代码如下:

class Welcome extends React.Component {handleClick = (num) => {   // 高阶函数return (ev) => {console.log(num);}}render(){return (<div><button onClick={this.handleClick(123)}>点击</button>hello world</div>);}
}
let element = (<Welcome />
);

鼠标事件 mouseenter与mouseover区别

mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发

mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。

mouseenter 不支持事件冒泡 mouseover 会冒泡

跨组件通信

Welcome传递给Title:

let MyContext = React.createContext();
class Welcome extends React.Component {state = {msg: 'welcome组件的数据'}render(){return (<div>Hello Welcome<MyContext.Provider value={this.state.msg}><Head /></MyContext.Provider></div>);}
}
class Head extends React.Component {render(){return (<div>Hello Head<Title /></div>);}
}
class Title extends React.Component {static contextType = MyContextcomponentDidMount = () => {console.log( this.context );}render(){return (<div>Hello Title <MyContext.Consumer>{ value => value }</MyContext.Consumer></div>);}
}
let element = (<Welcome />
);

通过<MyContext.Provider>组件携带value属性进行向下传递的,

那么接收的语法是通过<MyContext.Consumer>组件。

也可以定义一个静态方法static contextType = MyContext,这样就可以在逻辑中通过this.context来拿到同样的值。

生命周期

生命周期钩子函数就是回调函数

挂载

  • constructor():在 React 组件挂载之前,会调用它的构造函数。(注:如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。)
  • render(): class 组件中唯一必须实现的方法。
  • componentDidMount():在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。

更新

  • render(): class 组件中唯一必须实现的方法。
  • componentDidUpdate():在更新后会被立即调用。首次渲染不会执行此方法。

卸载

  • componentWillUnmount():在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
     

可以看到挂载时和更新时都有render这个方法。这就是为什么state改变的时候,会触发render重渲染操作

class Welcome extends React.Component {state = {msg: 'hello world'}constructor(props){super(props);console.log('constructor');}componentDidMount = () => {// react中发起ajax请求的初始操作,在这个钩子中完成console.log('componentDidMount');}componentDidUpdate = () => {// 等DOM更新后触发的钩子console.log('componentDidUpdate');}componentWillUnmount = () => {console.log('componentWillUnmount');}handleClick = () => {  /* this.setState({msg: 'hi react'}); *///this.forceUpdate();root.unmount();   // 触发卸载组件}render(){console.log('render');return (<div><button onClick={this.handleClick}>点击</button>{ this.state.msg }</div>);}
}

状态提升

多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。对子组件操作,子组件不改变自己的状态。

复用组件

Render Props模式

组件之间使用一个值为函数prop 共享代码的简单技术。

class MouseXY extends React.Component {state = {x: 0,y: 0}componentDidMount = () => {document.addEventListener('mousemove', this.move)}componentWillUnmount = () => {document.removeEventListener('mousemove', this.move)}move = (ev) => {this.setState({x: ev.pageX,y: ev.pageY});}render(){return (<React.Fragment>{ this.props.render(this.state.x, this.state.y) }</React.Fragment>);}
}
class Welcome extends React.Component {render(){return (<MouseXY render={(x, y)=> <div>hello world, {x}, {y}</div>} />);}
}
let element = (<Welcome />
);

render属性后面的值是一个回调函数,通过这个函数的形参可以得到组件中的数据,从而实现功能的复用。

HOC高阶组件模式

参数组件返回值新组件函数

function withMouseXY(WithComponent){return class extends React.Component {state = {x: 0,y: 0}componentDidMount = () => {document.addEventListener('mousemove', this.move)}componentWillUnmount = () => {document.removeEventListener('mousemove', this.move)}move = (ev) => {this.setState({x: ev.pageX,y: ev.pageY })}render(){return <WithComponent {...this.state} />}}
}
class Welcome extends React.Component {render(){return (<div>hello world, { this.props.x }, { this.props.y }</div>);}
}
const MouseWelcome = withMouseXY(Welcome)
let element = (<MouseWelcome />
);

Hooks

Hook 是 React 16.8 的新增特性,是一个特殊的函数,它可以在不写 class(不用extends React.Component) 的情况下“钩入” React 特性组件化模块的特性

useState

等同组件中的setState()

let { useState } = React;//只能在最顶层使用Hook
let Welcome = (props) => {//只能在函数组件中使用Hook//count的初始值0,设置count的函数const [count, setCount] = useState(0);const handleClick = () => {setCount(count + 1)       }return (<div><button onClick={handleClick}>点击</button><div>hello world, { count }</div></div>);
}

与组件中的state一样自动批处理(即合并修改,每次set只渲染一次),可用flushSync方法消除

(flush synchronization清洗同步)

setCount((count)=> count+1)
setCount((count)=> count+1)
setCount((count)=> count+1)<div>{ count }</div>   // 渲染 3let { useState } = React;
let { flushSync } = ReactDOM;
let Welcome = (props) => {const [count, setCount] = useState(0);const [msg, setMsg] = useState('hello');const handleClick = () => {flushSync(()=>{setCount(count + 1)})flushSync(()=>{setMsg('hi')})       }return (<div><button onClick={handleClick}>点击</button><div>hello world, { count }, { msg }</div></div>);
}

useState中的值在修改的时候,并不会进行原值的合并处理,所以使用的时候要注意。可利用扩展运算符的形式来解决合并的问题。

const [info, setInfo] = useState({username: 'xiaoming',age: 20
})
setInfo({...info,username: 'xiaoqiang'
})

如果遇到初始值需要大量运算才能获取的话,可采用惰性初始state,useState()添加回调函数的形式来实现。

const initCount = () => {console.log('initCount');return 2*2*2;
}
const [count, setCount] = useState(()=>{return initCount();
});

这样初始只会计算一次,并不会每次都重新进行计算

useReducer

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。

let { useReducer } = React;
let loginState = {//体现整体关联性与统一性**isLogin: true,isLogout: false
}
let loginReducer = (state, action) => {switch(action.type){case 'login':return { isLogin: true, isLogout: false }case 'logout':return { isLogin: false, isLogout: true }default: throw new Error() }
}
let Welcome = (props) => {  const [ state, loginDispatch ] = useReducer(loginReducer, loginState);const handleLogin = () => {loginDispatch({ type: 'login' });}const handleLogout = () => {loginDispatch({ type: 'logout' });}return (<div>{ state.isLogin ? <button onClick={handleLogout}>退出</button> : <button onClick={handleLogin}>登录</button> }</div>);
}

useEffect

在函数组件中执行副作用操作,副作用即:DOM操作、获取数据、记录日志

代替类组件中的生命周期钩子函数。
如果不传参:相当于render之后就会执行
如果传空数组:相当于componentDidMount
如果传数组:相当于componentDidUpdate
如果返回回调函数:相当于componentWillUnmount,会在组件卸载的时候执行清除操作。

effect 在每次渲染的时候都会执行。React 会在执行当前 effect 之前对上一个 effect 进行清除。

let Welcome = (props) => {const [count, setCount] = useState(0);//异步函数,在浏览器渲染DOM后触发的useEffect(()=>{// 初始 和 更新 数据的时候会触发回调函数console.log('didMount or didUpdate');return ()=>{  // 这里回调函数可以用来清理副作用console.log('beforeUpdate or willUnmount');}})const handleClick = () => {//setCount(count + 1);root.unmount();//卸载}return (<div><button onClick={handleClick}>点击</button><div>hello world, { count }</div></div>);
}

关注点分离后,改变一个数据后,例如count,那么msg相关的useEffect也会触发,

给useEffect设置第二个参数,只重新触发自己的useEffect回调函数,即响应式的数据

const [count, setCount] = useState(0);
useEffect(()=>{console.log(count);
}, [count])
const [msg, setMsg] = useState('hello');
useEffect(()=>{console.log(msg);
}, [msg])
  • useEffect()是在渲染之后屏幕更新之后,,是异步的;
  • useLayoutEffect()是在渲染之后但在屏幕更新之前,是同步的。

大部分情况下我们采用useEffect()性能更好

但当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式

就需要useLayoutEffect,否则可能会出现闪屏问题。

useRef

let { useRef } = React;let Welcome = (props) => {  const myRef = useRef()...}等同于React.createRef()

函数转发

把ref添加到函数组件上,把ref对应的对象转发到子组件的内部元素身上。

let Head = React.forwardRef((props, ref) => {return (<div>hello Head<input type="text" ref={ref} /></div>)
})
let Welcome = (props) => {  const myRef = useRef()const handleClick = () => {myRef.current.focus();}return (<div><button onClick={handleClick}>点击</button><Head ref={myRef} /></div>);
}

    let count = useRef(0);  // 与state类似,有记忆功能,可理解为全局操作
    const handleClick = () => {
        count.current++;

...

}

useCallback和useMemo

React 使用 Object.is 比较算法 来比较 state。

当组件数据没有变化时,是不会重新渲染试图,如下,cosole.log不会执行

let Welcome = (props) => {  const [ count, setCount ] = useState(0);const handleClick= () => {setCount(0);}console.log(123);return (<div><button onClick={handleClick}>点击</button>hello Welcome { Math.random() }</div>);
}

变化后state与当前state相同时,包括变化前的渲染一共会渲染两次

因为React 可能仍需要在跳过渲染前 渲染该组件

但React 不会对组件树的“深层”节点进行不必要的渲染

React.memo避免可以没有必要的重新渲染,类似于类组件中的纯函数概念。

将{ Math.random() } 包装成函数组件

let Welcome = (props) => {  const [ count, setCount ] = useState(0);const handleClick= () => {setCount(1);}return (<div><button onClick={handleClick}>点击</button>hello Welcome<Head count={count} /></div>);
}
let Head = React.memo(() => {return (<div>hello Head, { Math.random() }</div>)
})

渲染期间执行了高开销的计算,则可以使用 useMemo 来进行优化

useCallback返回一个可记忆函数,useMemo返回一个可记忆的,useCallback只是useMemo的一种特殊形式

let Welcome = (props) => {  const [ count, setCount ] = useState(0);const handleClick= () => {setCount(count+1);}const foo = () => {}return (<div><button onClick={handleClick}>点击</button>hello Welcome<Head onClick={foo} /></div>);
}
let Head = React.memo(() => {return (<div>hello Head, { Math.random() }</div>)
})

当点击按钮的时候,<Head>组件会进行重新渲染,因为每次重新触发<Welcome>组件的时候,后会重新生成一个新的内存地址的foo函数。

通过useCallback和useMemo可以不让foo函数重新生成,使用之前的函数地址

从而减少子组件的渲染,提升性能

const foo = useCallback(() => {}, [])const foo = useMemo(()=> ()=>{}, [])   // 针对函数
const bar = useMemo(()=> [1,2,3], [])  // 针对数组const foo = useMemo(()=> ()=>{}, [count])   // 第二个参数为依赖项,当count改变时,函数重新创建

自定义Hook

实现函数组件复用

let { useState, useEffect } = React
let useMouseXY = () => {const [x, setX] = useState(0)const [y, setY] = useState(0)useEffect(()=>{function move(ev){setX(ev.pageX)setY(ev.pageY)}document.addEventListener('mousemove', move)//如果返回回调函数:相当于componentWillUnmount,会在组件卸载的时候执行清除操作。return () => {document.removeEventListener('mousemove', move)}}, [])//如果传空数组:相当于componentDidMountreturn {x,y}
}
let Welcome = ()=>{const {x, y} = useMouseXY()return (<div>hello Welcome, {x}, {y}</div>)
}

StrictMode严格模式

StrictMode 是一个用来突出显示应用程序中潜在问题的工具。用于开发环境

与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。例如:

  • 识别不安全的过时的生命周期
  • 关于使用过时字符串 ref API 的警告

发布环境下关闭严格模式,以避免性能损失。

Router

路由是根据不同的url地址展示不同的内容或页面,是SPA(单页应用)的路径管理器

1.一个路由就是一个映射关系(key:value)

2.key为路径, value可能是function或component

Router 用于定义多个路由,当用户定义特定的 URL 时,如果此 URL 与 Router 内定义的任何 “路由” 的路径匹配,则用户将重定向到该特定路由。

路由模式(同Vue)

React中的路由模式跟Vue中一样,分为history和hash模式。默认是hash

Hash模式

hash——即地址栏URL中的#符号(此hash不是密码学里的散列运算)。比如在 http://localhost:8080/#/donate 中,hash的值就是#/donate,我们在浏览器中可以通过BOM中的window.location.hash来获取当前URL的hash值

注:BOM(Browser Object Model) 是指浏览器对象模型,BOM由多个对象组成,其中代表浏览器窗口的Window对象是BOM的顶层对象,其他对象都是该对象的子对象。

history模式

通过在host后,直接添加斜线路径请求后端的一种URL模式

图中pathname变量为/donate,而hash值为空。

原理

  • hash通过window.addEventListener监听浏览器的onhashchange()事件变化,查找对应的路由规则
  • HTML5history API监听URL变化,所以有浏览器兼容问题

区别

  • 是否向后端传参:

    在hash模式中,我们刷新一下上面的网页,可以看到请求的URL为http://localhost:8080/,没有带上#/donate,说明hash 虽然出现 URL 中,#后面的内容是不会包含在http请求中的,对后端完全没有影 响,因此改变 hash 不会重新加载页面。

在history模式中,刷新一下网页,明显可以看到请求url为完整的url,url完整地请求了后端:

前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误

 

在React中

  • history模式:createBrowserRouter

IE9及以下不兼容,需要由web server支持,在web client这边window.location.pathnamereact router解析

  • hash模式:createHashRouter

不需要由web server支持,因为它的只有‘/’path需要由web server支持,而#/react/route URL不能被web server读取,在web client这边window,location.hashreact router解析

history的好处是可以进行修改历史记录,并且不会立刻像后端发起请求。不过如果对于项目没有硬性标准要求,可以直接使用hash模式开发。

基础路由搭建

import { createBrowserRouter, createHashRouter } from 'react-router-dom'
//路由表
export const routes = [];
//路由对象
const router = createBrowserRouter(routes);
export default router;

接下来让路由配置文件与React结合,需要在主入口index.js进行操作,如下:

import { RouterProvider } from 'react-router-dom'
import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<React.StrictMode><RouterProvider router={router}></RouterProvider></React.StrictMode>
);

路由表的配置字段如下:

  • path:指定路径

  • element:对应组件

  • children:嵌套路由

//路由表
export const routes = [{path: '/',element: <App />,children: [{path: '',element: <Home />},{path: 'about',element: <About />,children: [{path: 'foo',element: <Foo />,},{path: 'bar',element: <Bar />,}]}]}
];

接下来就是显示路由区域,利用<outlet>组件占位,表明子路由渲染的位置

import React from "react";
import { Outlet, Link } from 'react-router-dom'
function App() {return (<div className="App"><h2>hello react</h2><Link to="/">首页</Link> | <Link to="/about">关于</Link><Outlet /></div>);
}
export default App;

可以看到 <Link>组件用于声明式路由切换使用。同样<outlet>组件也可以给嵌套路由页面进行使用,从而完成二级路由的切换操作。

import React from 'react'
import './About.scss'
import { Outlet, Link } from 'react-router-dom'
export default function About() {return (<div><h3>About</h3><Link to="/about/foo">foo</Link> | <Link to="/about/bar">bar</Link><Outlet /></div>)
}
​

重定向路由

访问的URL跳转到另一个URL上,从而实现重定向的需求。

<Navigate>组件就是实现重定向需求的组件。

import { createBrowserRouter, createHashRouter, Navigate } from 'react-router-dom'
children: [ {index: true,// 默认路由element: <Navigate to="/about/foo/123" />,},{path: 'foo',element: <Foo />},{path: 'bar',element: <Bar />}]

自定义全局守卫

全局守卫:包裹根组件,访问根组件下面的所有子组件都要先通过守卫进行操作

在React里面,它不像Vue一样,为我们提供和很多方便的功能,一些功能都需要自己去进行封装比如说路由拦截

在/src/components/BeforeEach.jsx下创建守卫的组件。

import React from 'react'
import { Navigate } from 'react-router-dom'
import { routes } from '../../router';
export default function BeforeEach(props) {if(true){return <Navigate to="/login" />}else{return (<div>{ props.children }</div>)}
}

根据判断的结果,是否进入到组件内,还是重定向到其他的组件内。

调用BeforeEach.jsx,index.js通过路由配置文件引入,如下:

export const routes = [{path: '/',element: <BeforeEach><App /></BeforeEach>//包裹根组件APP}
]

动态路由

根据不同的URL,可以访问同一个组件。在React路由中,通过path字段来指定动态路由的写法。

foo/xxx都能访问到Foo组件,本身就有实现了动态路由

import { Outlet, Link } from 'react-router-dom'
export default function About() {return (<div><Link to="/about/foo/123">foo 123</Link> | <Link to="/about/foo/456">foo 456</Link></div>)
}
{path: 'foo/:id',element: <Foo />
}

id就是变量名,可以在组件中用useParams来获取到对应的值。

import { useParams } from 'react-router-dom'
export default function Foo() {const params = useParams()return (<div>Foo, { params.id }</div>)
}

Redux状态管理库

Redux就像Vue中的VuexPinia是一样专门用于做状态管理的JS库(不是react插件库)。

只不过Redux比较独立,可以跟很多框架结合使用,不过主要还是跟React配合比较好,也是最常见的React状态管理的库。

redux相当于在顶层组件之上又加了一个组件

//state存储共享数据
function counterReducer(state={count: 0}, action) {//reducer修改stateswitch(action.type){case 'inc':return {count: state.count + state.payload}default:return state;}
}
const store = createStore(counterReducer)
export default store

这样store对象就可以在其他组件中进行使用了,例如在<Bar>组件中。

import React from 'react'
import './Bar.scss'
import { useSelector,useDispatch } from 'react-redux'
export default function Bar() {const count = useSelector((state)=> state.count)//获取共享状态const dispatch=useDispatch();//修改共享状态const handleClick = () => {dispatch({//dispatch触发Reducer,分发actiontype: 'inc',payload: 5})}return (<div><button onClick={handleClick}>修改count</button>     Bar, { count }</div>)
}

在主模块中进行注册。

import { RouterProvider } from 'react-router-dom'
import router from './router';
import { Provider } from 'react-redux'
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<React.StrictMode><Provider store={store}>//注册状态管理与React结合,自动完成重渲染<RouterProvider router={router}></RouterProvider></Provider></React.StrictMode>
);

应用

从项目的整体看

  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了 WebSocket
  • View 要从多个来源获取数据

从组件角度看

  • 某个组件的状态,需要共享
  • 组件有相当大量的,随时间变化的数据
  • state 需要有一个单一可靠数据源

React脚手架

开始一个react项目,不用手动配置,直接开发

Angular,Vue,React对比


Angular

框架比较成熟完整,过于庞大,上手难;

React和Vue

相同点

创建UI的js库

都是使用了虚拟dom

组件化开发

父子之间通信单项数据流

都支持服务端渲染

不同点

vue轻量级框架,其核心库只关注视图层,简单小巧、易学易上手;

个人维护项目; 适合于移动端开发; 

reacct 的jsx,可读性好

vue的是 template

数据变化,react 手动 setState vue自动响应式处理 proxy object.DefineProperty

react 单向数据流 ,vue双向数据流

react 的 redux mobx

vue 的vuex  pinia

MVC、MVP、MVVM模式

MVC (Model View Controller)

  • Model(模型):提供数据
  • View(视图):显示数据
  • Controller(控制器):用户交互

【优点】

耦合性低,方便维护,可以利于分工协作 重用性高

【缺点】

使得项目架构变得复杂,对开发人员要求高

MVP(Model View Presenter)

从MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,

MVVM (Model View View Model)

视图业务逻辑分开。

View视图层Model 数据模型ViewModel 是它们双向绑定的桥梁,自动同步更新

【优点】

相比mvp各层的耦合度更低,一个viewmodel层可以给多个view层共用(一对多),提高代码的可重用性

*耦合度:模块间依赖的程度。

【缺点】

因为使用了dataBinding,增加了大量的内存开销,增加了程序的编译时间,所以适合轻量级项目。

数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题

React和Vue都用了MVVM

React单向数据流:只能由数据层的变化去影响视图层的变化

Vue双向数据绑定


http://www.ppmy.cn/news/37474.html

相关文章

LightGBM常见面试题总结

算法工程师常见面试问题总结之LightGBM常见面试题总结 理论知识&#xff1a;通俗易懂的LightGBM理论知识 LightGBM是什么&#xff1a;LightGBM&#xff08;Light Gradient Boosting Machine&#xff09;是一个实现GBDT算法的框架&#xff0c;支持高效率的并行训练&#xff0c;并…

蓝桥杯刷题冲刺 | 倒计时5天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录1.方格迷宫2.字符串删减1.方格迷宫 题目 链接&#xff1a; 4943. 方格迷宫 - AcWing题库 给定一…

2023年南京晓庄学院五年一贯制专转本食品科学与工程专业考试大纲

2023年南京晓庄学院五年一贯制专转本食品科学与工程专业考试大纲 专业科目一 &#xff1a;微生物学基础 【参考书目】《食品微生物学》&#xff0c;杨玉红主编&#xff0c; 中国质检 出版社/中国标准出版社&#xff0c;2017(十三五高职高专院校规划教材) 【考试大纲】 ( 一) 考…

WLAN速度突然变慢

目录 一、问题 二、在设置中重置网络 1. 按下组合键“WinI”打开设置&#xff0c;在设置窗口中点击“网络和Internet”。 2、点击左侧的“状态”&#xff0c;在右侧选择“网络重置”。 3、然后会进入“网络重置”页面&#xff0c;点击“立即重置”后点击“是”等待完成即可…

某面试官分享经验:看求职者第一眼,开口说第一句话,面试结果就差不多定了,准确率高达90%以上...

我们以前分享过许多经验&#xff0c;但大多是站在打工人的视角上&#xff0c;今天给大家带来一个面试官的经验&#xff1a;1. 看求职者第一眼&#xff0c;开口说第一句话&#xff0c;面试结果就差不多定了&#xff0c;准确率高达90%以上。2. 绝不考八股文&#xff0c;如果问技术…

Ajax 入门

前端技术&#xff1a;在浏览器中执行的程序都是前端技术。如 html、css、js 等 后端技术&#xff1a;在服务器中执行的长须&#xff0c;使用 Java 等语言开发的后端程序。servlet&#xff0c;jsp&#xff0c;jdbc&#xff0c;mysql&#xff0c;tomacat 等 全局刷新 使用表单…

第04章_逻辑架构

第04章_逻辑架构 &#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目前在某…

【新2023Q2模拟题JAVA】华为OD机试 - 总最快检测效率 or 核酸检测效率

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:报数 题目 一百个人围成一圈…