const ele = (
赞
踩
构建用户界面
的 JavaScript 库UI
界面,这些代码片段被称作组件
JSX是什么呢?它就是js的语法拓展。
<script type="text/babel">
const ele = (<div className="box"><h1>hi React</h1></div>)
ReactDOM.render(ele,document.querySelector('#root'))
</script>
JSX的优点:
JSX 是
React.createElement
的语法糖
各位看官,我们再来瞅瞅这样一段代码。
// 创建react元素
const e = React.createElement
const ele = e('div',{className: 'box'},e('h1',null,'hi React'))
ReactDOM.render(ele,document.querySelector('#root'))
您瞧见没,通过React.createElement
创建出来的跟利用JSX
创建出来的内容完全一致。可一旦,代码多了起来呢?
"use strict";
/*#__PURE__*/
React.createElement("div", null, /*#__PURE__*/React.createElement("ul", null,
/*#__PURE__*/React.createElement("li", null,
/*#__PURE__*/React.createElement("span", {
className: "info"
}, "hello world")),
/*#__PURE__*/React.createElement("li", null,
/*#__PURE__*/React.createElement("span", null, "hello world")),
/*#__PURE__*/React.createElement("li", null,
/*#__PURE__*/React.createElement("span", null, "hello world"),
/*#__PURE__*/React.createElement("div", null, "hello world"))));
是不是就复杂了起来?这么创建元素会比较复杂,成本高,没有可读性,不利于维护以及不能复用。那么,就用到了上方的JSX。
命令:
yarn add react@next react-dom@next
yarn add @babel/standalone
类式组件
在react 16.8.0之前,用类定义组件。基于类并继承父类React.Component组件,子类就能使用react所提供的特性
class Foo1 extends React.Component{
render() {
return <h1>这里是类式组件</h1>
}
}
ReactDOM.render(
<Foo1 />,
document.querySelector('#root')
)
注意:类式组件过于臃肿复杂,不利于代码复用
函数式组件
在react 16.8.0后 (2019年2月6日),推荐函数式编程,用函数定义组件。
新增了Hooks特性,一种无需编写类即可使用状态和其他 React 特性的方法
const App = () => {
return (
<div>
<h1>
这里是函数式组件
</h1>
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<App/>)
就像是官网文档写的那样,当你第一次安装并运行时,你会在控制台报一个错,React 18 不再支持 ReactDOM.render,请改用 createRoot。
请注意: 各位看官,一定要拥抱
函数式编程
,接下来当使用react-router-dom v6
版本的时候,会由巨大决策。
Create React App
是学习React的一个舒适的环境,也是开始在React中创建一个新的单页应用的最佳方式。在安装create-react-app
之前,要确保是否已经安装node
。
安装yarn包管理工具
npm i yarn -g
由于淘宝镜像地址变更,于是重新配置淘宝镜像。
yarn config set registry https://registry.npmmirror.com/
上述安装完毕后,从而可以继续安装
create-react-app
安装方式yarn add global create-react-app
或者npm install -g create-react-app
然后通过命令create-react-app my-app
或者yarn create react-app my-app
创建react项目。
不过,创建完项目后,我们来看看package.json
文件,此时,项目中react
和react-dom
已经更新为18.0.0
版本。而在npmjs.com上,react18也处于最新版本。
回到项目中,我们使用yarn start
或者npm start
运行项目时,会打开一个默认的3000端口,而后,再来看看控制台有什么信息。
它出现了一个警告:
这是什么意思呢?
简单来说,就是 React 18不再支持渲染,使用createRoot代替。
那么,我们回到src/index.js
文件中,进行稍微的修改。
import React from 'react';
import {createRoot} from 'react-dom/client';
import App from './App';
const element = document.querySelector('#root')
const root = createRoot(element);
root.render(<App/>)
然后回到浏览器中,在控制台上就没有了那个警告。
Hook是什么?Hook 它是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
总之,Hook 是一个特殊的函数
.
在说这个Hook之前,了解一下什么是批处理。
批处理是指React将多个状态更新分组到一个单独的小组,然后通过重新渲染此单独的小组来获得更好的性能。如果你在同一个点击事件中要更新两个状态,那么React 总会将它们批处理到一个渲染中。
参考资料
function App() { const [count, setCount] = useState(0); const [flag, setFlag] = useState(false); function handleClick() { setCount(c => c + 1); // 还没有重新渲染 setFlag(f => !f); // 还没有重新渲染 // React只会在最后重新渲染一次(这就是批处理) } return ( <div> <button onClick={handleClick}>Next</button> <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1> </div> ); }
在React 18之前,我们只在React事件处理程序中批量更新。默认情况下,promise、setTimeout、本机事件处理程序或任何其他事件中的更新都没有在React中批处理。
从React 18的createRoot
开始,所有的更新都会自动批处理
,不管它们来自哪里。
这意味着 timeouts, promises, 本机事件处理程序或任何其他事件中的更新将以与React事件中的更新相同的方式进行批处理。我们希望这能减少渲染工作,从而提高应用程序的性能:
function App() { const [count, setCount] = useState(0); const [flag, setFlag] = useState(false); function handleClick() { fetchSomething().then(() => { // React 18和以后会分批处理这些: setCount(c => c + 1); setFlag(f => !f); // React只会在最后重新渲染一次(这就是批处理) }); } return ( <div> <button onClick={handleClick}>Next</button> <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1> </div> ); }
如果我们不想使用自动批处理呢?那么就需要使用ReactDOM.flushSync
handleClick = () => {
setTimeout(() => {
ReactDOM.flushSync(() => {
this.setState(({ count }) => ({ count: count + 1 }));
});
// { count: 1, flag: false }
console.log(this.state);
this.setState(({ flag }) => ({ flag: !flag }));
});
};
// UseState.jsx
import {useState} from "react";
const UseState = () => {
const [count, setCount] = useState(0)
return (
<>
<h2>UseState--{count}</h2>
<button onClick={()=>{
setCount(count=>count+1)
}}>点击加一</button>
</>
)
}
export default UseState
在上述代码中:
useState
Hook。它让我们在函数组件中存储内部 state。useState
Hook 声明了一个新的 state 变量。它返回一对值给到我们命名的变量上。同时,将这个变量名设置为count
,此外,这个count
拥有一个默认值为0
。第二个返回的值本身就是一个函数。它让我们可以更新 count 的值,所以我们叫它 setCount
。setCount
。React 会重新渲染 UseState
组件,并把最新的 count 传给它。如果,我们想封装一个hooks,然后复用呢?那么就来封装一个自定义hook
吧。
根据规范要求,我们所自定义的hook一定也是以use
开头的。
//useCountState.js
import {useState} from "react";
export default ()=> {
const [count, setCount] = useState(0)
const handleClickCount = () => {
setCount(count=>count+1)
}
return [count, handleClickCount]
}
其中,你用到什么,你就return什么。
const UseState = () => {
const [count, handleClickCount] = useCountState()
return (
<>
<h2>UseState--{count}</h2>
<button onClick={handleClickCount}>点击加一</button>
</>
)
}
export default UseState
import {useEffect, useState} from "react"; const UseEffect = () => { const [count, setCount] = useState(0) useEffect(()=>{ document.title = count.toString() + Math.random() }) return ( <> <h2>UseEffect--{count}</h2> <button onClick={()=>{ setCount(count=>count+1) }}>加一</button> </> ) } export default UseEffect
通过上方这个案例,使用useEffect
这个 Hook,可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数,并且在执行 DOM 更新之后调用它。在这个 effect 中,我们设置了 document 的 title 属性,不过我们也可以执行数据获取或调用其他命令式的 API。
当我们使用到这个组件时,点击加一按钮,看看这个页面的标题会发生什么变化?
默认情况下,它在第一次一次点击加一操作和之后每一次更新时,都会重新渲染一次,其实从本质上来说这根本没必要多次执行。
于是,我们传第二个参数
,如果是空数组,代表只执行一次。相当于window.onload。如下图所示:
import {useEffect, useState} from "react"; const UseEffect = () => { const [count, setCount] = useState(0) useEffect(()=>{ document.title = count.toString() + Math.random() },[]) return ( <> <h2>UseEffect--{count}</h2> <button onClick={()=>{ setCount(count=>count+1) }}>加一</button> </> ) } export default UseEffect
尽管,count
的值仍然加一,但是,title
却只渲染一次。
在其中,有一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露。
import {useEffect, useState} from "react"; const UseEffect = () => { const [count, setCount] = useState(0) useEffect(()=>{ const title = document.querySelector('#title') let timer = setInterval(()=>{ setCount(count=>count+1) },1000) const click = () => { console.log('我点击了') } title.addEventListener('click',click) return ()=> { // 去做清除的事情 // 为了防止内存泄漏, 先清除上一轮的effect // 处理副作用 clearInterval(timer) title.removeEventListener('click',click) } },[]) return ( <> <h2 id='title'>UseEffect--{count}</h2> <button >加一</button> </> ) } export default UseEffect
推荐接口测试地址:https://cnodejs.org/api
import {useEffect, useState} from "react"; const UseEffect = () => { const [list, setList] = useState([]) const [id, setId] = useState('5433d5e4e737cbe96dcef312') const [num, setNum] = useState(0) const [info, setInfo] = useState([]) useEffect(()=>{ const requestList = async () => { try { const response = await fetch('https://cnodejs.org/api/v1/topics') const res = await response.json() console.log(res['data']) setList(res['data']) } catch (err) { if (err) throw Error } } requestList().then((rs)=>{ console.log('requestList') }) },[]) useEffect(()=>{ const requestInfo = async () => { try { const response = await fetch('https://cnodejs.org/api/v1/topic/' + id) const res = await response.json() setInfo(res['data']) } catch (err) { if (err) throw Error } } requestInfo().then((rs)=>{ console.log('requestInfo') }) },[id]) return ( <> <h2 id='title'>UseEffect</h2> <h4>{id}</h4> <div style={{display:'flex'}}> <ul> { list.map((item, index)=>{ return ( <li key={item.id} style={{background:index === num ? 'skyblue':'white' ,cursor:"pointer"}} onClick={()=>{ setId(item.id) setNum(index) }} >{item.id}</li> ) }) } </ul> <div dangerouslySetInnerHTML={{__html: info.content}}> </div> </div> </> ) } export default UseEffect
useState
进行初始值的设置,以及进行值的更新。useEffect
进行数据的渲染,当在useEffect
中的第二个参数内,传了一个id
,而这个id是useState
内的值,也可以将它理解为一个监视器,当这个id的值
发生变化,那么我就在次触发useEffect
从而进行数据的渲染。info
值时,我们不妨来看看它里面是个什么东西。content
里面的内容,于是,就用到了dangerouslySetInnerHTML={{__html: info.content}}
这种代码,就类似vue的v-html,将字符串转换成html代码,并渲染到页面之中。总体演示效果:
我们比较常用的是useEffect
这个hook,当然在官方文档中,当它出问题的时候再尝试使用useLayoutEffect
,那么useLayoutEffect
与useEffect
的区别是什么呢?
useEffect
是异步执行的,useLayoutEffect
是同步执行的。useLayoutEffect
是当浏览器把内容渲染到页面之前执行,而useEffect
是当浏览器把内容渲染到页面之后执行。尽可能使用标准的 useEffect
以避免阻塞视觉更新。这里,我选择使用gsap库来举例
安装方式:yarn add gsap
import gsap from 'gsap' import {useLayoutEffect, useRef} from "react"; const UseLayoutEffect = () => { const ele = useRef(null); useLayoutEffect(()=>{ gsap.to(ele.current,{duration:0,x:200}) },[]) return ( <> <div ref={ele} style={{ width: '300px', height: '300px', background:'skyblue' }} > </div> </> ) } export default UseLayoutEffect
看起来,直接出现在页面中,而useeffect
则会闪烁一下。这里就不再展示useEffect的代码,可以自己尝试一下。
综上所述,useLayoutEffect
是渲染之前同步执行的,于是,会等到动画执行完再渲染到页面上,也就没有闪烁一下的状态。而useeffect
刚好与之相反。
在我们爷孙之间传值的时候,如果使用两层父子更新是不是比较麻烦。于是React
给出了一个Hook,名为useContext
。
通过 createContext
创建出来的上下文,在子组件及后代组件中可以通过 useContext
这个 Hook 获取 Provider 提供的内容。
如果要使用上下文的内容,需要通过Context.Provider
最外层包装组件,比如GoodsContent.Provider
,同时,一定要给一个value
值,指定要暴露的信息。
const GoodsContent = createContext(null)
在这行代码中,创建了一个上下文,然后createContext(defaultValue)
中的defaultValue实际上是一个默认值,在这里你定义什值都可以。如果匹配不到最新的 Provider ,那么就会使用该默认值。
import {createContext, useContext, useState} from "react"; const GoodsContent = createContext(null) const UseContext = () => { const [goods, setGoods] = useState({ fruits: { apple: 'apple', price: 14 }, meat: { beef: 'beef', price: 26 } }) const setGoodsPrice = () => { setGoods({...goods, price: goods.meat.price++}) } return ( <> <GoodsContent.Provider value={{...goods, setGoodsPrice}}> <Kinds/> </GoodsContent.Provider> </> ) } const Kinds = () => { const goods = useContext(GoodsContent) console.log(goods) return ( <> <h1>Kinds</h1> <h2>{goods.fruits.apple}--{goods.fruits.price}</h2> <h2>{goods.meat.beef}--{goods.meat.price}</h2> <HandleClickChangePrice/> </> ) } const HandleClickChangePrice = () => { const goods = useContext(GoodsContent) return ( <> <button onClick={()=>{ goods.setGoodsPrice() }}>改变价格</button> </> ) } export default UseContext
在某些场景下,useReducer
会比 useState
更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。
import {useReducer} from "react"; const initState = { count: 100, list: ['vue', 'react', 'flutter', 'electron'] } const reducer = (state, action) => { switch (action.type) { case 'increment': return {...state, count: state.count + action.step} case 'decrement': return {...state, count: state.count - action.step} default: return state } } const UseReducer = () => { const [state, dispatch] = useReducer(reducer, initState) return ( <> <h2>UseReducer</h2> <button onClick={()=>{ dispatch({type: 'increment', step: 100}) }}>count+: {state.count}</button> <button onClick={()=>{ dispatch({type: 'decrement', step: 100}) }}>count-: {state.count}</button> <ul> { state.list.map((item, index)=>{ return ( <li key={index}>{item}</li> ) }) } </ul> </> ) } export default UseReducer
const [state, dispatch] = useReducer(reducer, initState)
在这里,useReducer
接受了2个参数。
把“创建”函数和依赖项数组作为参数传入useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值
。这种优化有助于避免在每次渲染时都进行高开销的计算。
避免在渲染渲染过程中,因大量不必要的在耗时计算而导致的性能问题。
//useMemo.jsx import Child from "./Child"; import {useMemo, useState} from "react"; const UseMemo = () => { const [count, setCount] = useState(0) const [value, setValue] = useState(1000) // 避免在渲染渲染过程中 因大量不必要的在耗时计算而导致的性能问题 const cache = useMemo(()=>{ return count + 10 // 当 count 发生变化时 才会进行计算 },[count]) return ( <> <h2>UseMemo</h2> <h3> count: {count} <hr/> value: {value} </h3> <button onClick={()=>{setCount(count+1)}}>count++</button> <button onClick={()=>{setValue(value+1)}}>value++</button> <hr/> <Child count={cache}/> {/*使用缓存 而不是用一次计算一次*/} <Child count={cache}/> <Child count={cache}/> </> ) } export default UseMemo
//Child.jsx
import {memo} from "react";
const Child = (props) => {
console.log('----child -render-----')
const { count } = props
return (
<>
<h2>Child--{count}</h2>
</>
)
}
export default memo(Child)
在Child.jsx
组件中:使用到了React.memo
高阶组件。如果你的组件在相同 props 的情况下渲染相同的结果,那么就可以利用 React.memo
进行包裹,从而进行调用。如果渲染的结果是相同的,那么将跳过渲染返回最近一次的渲染结果。
const cache = useMemo(()=>{
return count + 10
// 当 count 发生变化时 才会进行计算
},[count])
因为我提供依赖项数组,于是乎useMemo
只有在count值发生变化的时候,才会进行计算。反之则会在每次渲染时都会计算新的值。
useCallback
返回一个 memoized 回调函数。
把内联回调函数及依赖项数组作为参数传入 useCallback
,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
来看一下没有使用useCallback
的效果。
import {useState} from "react"; const Child = ({value,change}) => { console.log('----re-render----') return ( <input type="text" value={value} onChange={change}/> ) } const UseCallback = () => { const [v1, setV1] = useState('') const [v2, setV2] = useState('') const onChange1 = (e)=>{ setV1(e.target.value) } const onChange2 = (e)=>{ setV2(e.target.value) } return ( <> <h2>UseCallback</h2> <Child value={v1} change={onChange1}/> <Child value={v2} change={onChange2}/> </> ) } export default UseCallback
我们能清晰的看到,在未使用useCallBack
时,任何一个输入框的变化,都会导致另一个组件重新渲染。
那么,再来看看使用这个hook的效果。
import {memo, useCallback, useState} from "react"; const Child = memo(({value,change}) => { console.log('----re-render----') return ( <input type="text" value={value} onChange={change}/> ) }) const UseCallback = () => { const [v1, setV1] = useState('') const [v2, setV2] = useState('') const onChange1 = useCallback((e)=>{ setV1(e.target.value) },[]) const onChange2 = useCallback((e)=>{ setV2(e.target.value) },[]) return ( <> <h2>UseCallback</h2> <Child value={v1} change={onChange1}/> <Child value={v2} change={onChange2}/> </> ) } export default UseCallback
任何一个输入框的变化,
都仅会导致自己的组件重新渲染。
useImperativeHandle
可以让你在使用 ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与forwardRef
一起使用。
import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react"; const ChildInput = (props,ref) => { const inputElement = useRef() useImperativeHandle(ref,()=>{ return { list: ['vue3','react18','flutter'], handleFocus() { inputElement.current.focus() } } }) console.log(props) return ( <> <input type="text" ref={inputElement}/> <div>children</div> <hr/> <hr/> {props.children} </> ) } const NewChildInput = forwardRef(ChildInput) const UseImperativeHandle = () => { const newIptEle = useRef() const [arr, setArr] = useState([]) useEffect(()=>{ console.log(newIptEle) setArr(newIptEle.current.list) newIptEle.current.handleFocus() },[]) return ( <> <h2>UseImperativeHandle</h2> <NewChildInput ref={newIptEle}> <ul> { arr.map((item, index)=>{ return ( <li key={index}>{item}-{index}</li> ) }) } </ul> </NewChildInput> </> ) } export default UseImperativeHandle
在上方代码中,React 会将 <NewChildInput ref={newIptEle}>
元素的 newIptEle
作为第二个参数传递给 ChildInput
函数中的渲染函数。该渲染函数会将 newIptEle
传递给 <input type="text" ref={inputElement}/>
元素。
为了快速使用路由,请先确保您的环境处于:
node.js版本 >= 12.x.x
npm或者yarn包管理工具
JavaScript,React.js和React Hooks的基础知识
首先,进入到整个项目的入口文件index.js
文件内,然后在这个文件内进行引入。
// index.js
import {
BrowserRouter as Router
} from 'react-router-dom'
同时,也需要将<App/>
包裹起来,就像这样:
// index.js
root.render(
<Router>
<App/>
</Router>
)
<NavLink>
是<Link>
的一个特定版本,会在匹配上当前的url的时候给已经渲染的元素添加参数。<NavLink>
是<Link>
的功能是一致的,区别在于可以判断其to属性是否是当前匹配到的路由。
而<NavLink>
也相当于一个a标签,我们可以直接给这个a标签添加激活时的样式。就比如:
a.active {
color: #61dafb;
font-weight: bold;
text-decoration: none;
}
当点击<NavLink>
时,会触发激活样式,字体会发生颜色等变化。
//App.js
import {
Route,
NavLink
} from 'react-router-dom'
//...
<nav className={'top-nav'}>
{/*链接*/}
<NavLink to='/'>发现音乐</NavLink>
<NavLink to='/myMusic'>我的音乐</NavLink>
<NavLink to='/friends'>朋友</NavLink>
<NavLink to='/download'>下载客户端</NavLink>
</nav>
Routes
这个新的元素是以前Switch
组件的升级版,它具有相对路由和链接、自动路由排名、嵌套路由和布局等功能。而且使用路由时,必须使用Routes将组件包裹
component
变成了element
,遇到地址时,会展示后面组件的内容,而且一定要写成组件的形式{<组件名/>}
。
而Route
必须被Routes
组件包裹。
<Routes>
<Route path='/' element={<HomePage/>}>
<Route index element={<RecommendPage/>}/>
<Route path='rank' element={<RankPage/>}/>
<Route path='songSheet' element={<SongSheetPage/>}/>
<Route path='singer' element={<SingerPage/>}/>
<Route path='newMusic' element={<NewMusicPage/>}/>
<Route path='playlist' element={<PlayListPage/>}/>
<Route path='course/:id' element={<CoursePage/>}/>
</Route>
<Route path='/myMusic' element={<MyMusicPage/>}/>
<Route path='/friends' element={<FriendsPage/>}/>
<Route path='/download' element={<DownloadPage/>}/>
<Route path='*' element={<NotFound404/>}/>
</Routes>
在Route中,index
与path
属性是不可得兼的,而且index
表示为当前路由的根。也就是在根路由下,默认显示<RecommendPage/>
组件的内容。
不过,这却不是我们想要的结果,效果如下图所示。
就算我们切换路由,但是仍没有出现剩余路由所对应的组件内容,那是因为什么呢?
是因为缺少了:Outlet
import { NavLink, Outlet } from "react-router-dom"; import '../sass/homePage.scss' const HomePage = () => { return ( <div className="sub-header"> <nav className="sub-nav"> <NavLink to='/'>推荐</NavLink> <NavLink to='/rank'>排行榜</NavLink> <NavLink to='/songSheet'>歌单</NavLink> <NavLink to='/singer'>歌手</NavLink> <NavLink to='/newMusic'>最新音乐</NavLink> </nav> <div> <Outlet/> </div> </div> ) } export default HomePage
然后,我们在<HomePage/>
组件写上Outlet
时,再来看看效果。
嵌套路由
一般需要与 Outlet
组件同时使用,此组件类似于Vue的router-view组件,告知子路由,将内容渲染至什么位置。
Navigate
代替了Redirect
组件,当在某个路径/a
下,要重定向到路径/b
时,就可以通过Navigate
组件进行重定向
import {
Navigate
} from 'react-router-dom'
const RankPage = () => {
return (
<>
<Navigate to='/download'/>
<h2>RankPage</h2>
</>
)
}
export default RankPage
当我们来到排行榜页面时,会重定向到下载页面。
获取查询参数。
使用useSearchParams
(是一个hook)来访问查询参数。其用法和useState类似。
import { useLocation, useSearchParams } from 'react-router-dom' // const queryString = require('query-string'); const PlayListPage = () => { // const search = useLocation() // console.log(queryString.parse(search.search)) const [searchParams,setSearchParams] = useSearchParams() console.log('id为',searchParams.get('id')) return ( <> <h2>PlayListPage</h2> </> ) } export default PlayListPage
在组件内通过useParams
访问路径参数。
import {useLocation, useParams} from "react-router-dom";
const CoursePage = () => {
const params = useParams()
console.log(params)
console.log(params?.id)
return (
<>
<h2>CoursePage</h2>
</>
)
}
export default CoursePage
此外,我们可以在link标签内写上state。保存在 location 中的 state。
// SingerPage.jsx import {Link} from "react-router-dom"; const SingerPage = () => { return ( <> <h2>SingerPage</h2> <div style={{display:'flex',justifyContent:'space-evenly'}}> <Link to='/course/111'>vu3</Link> <Link to='/course/222'>react18</Link> <Link to='/course/333' state={{id:123456,name:"i love react"}}>flutter</Link> </div> </> ) } export default SingerPage
import {useLocation, useParams} from "react-router-dom";
const CoursePage = () => {
const params = useParams()
console.log(params)
console.log(params?.id)
const {state:{name, id}} = useLocation()
console.log(name,id)
return (
<>
<h2>CoursePage</h2>
</>
)
}
export default CoursePage
使用useNavigate
钩子函数生成navigate
对象,代替了useHistory
,可以通过JS代码完成路由跳转。
import { useNavigate, Navigate } from 'react-router-dom' const NewMusicPage = () => { const navigate = useNavigate() return ( <> <h2>NewMusicPage</h2> <button onClick={()=>{ navigate('/friends') }} >编程导航实现页面跳转push有历史记录 可以前进后退</button> <br/> <button onClick={()=>{ navigate('/friends',{replace: true}) }} >替换当前 replace</button> </> ) } export default NewMusicPage
// src/router/index.js import HomePage from "../pages/HomePage"; import MyMusicPage from "../pages/MyMusicPage"; import FriendsPage from "../pages/FriendsPage"; import DownloadPage from "../pages/DownloadPage"; import NotFound404 from "../pages/NotFound404"; import RecommendPage from "../pages/Home/RecommendPage"; import RankPage from "../pages/Home/RankPage"; import SongSheetPage from "../pages/Home/SongSheetPage"; import SingerPage from "../pages/Home/SingerPage"; import NewMusicPage from "../pages/Home/NewMusicPage"; import PlayListPage from "../pages/Home/PlayListPage"; import CoursePage from "../pages/Home/CoursePage"; const routes = [ { path: '/', element: <HomePage/>, children: [ { path: '', index: true, element: <RecommendPage/> }, { path: 'rank', element: <RankPage/> }, { path: 'songSheet', element: <SongSheetPage/> }, { path: 'singer', element: <SingerPage/> }, { path: 'newMusic', element: <NewMusicPage/> }, { path: 'playlist', element: <PlayListPage/> }, { path: 'course/:id', element: <CoursePage/> } ] }, { path: '/myMusic', element: <MyMusicPage/> }, { path: '/friends', element: <FriendsPage/> }, { path: '/download', element: <DownloadPage/> }, { path: '*', element: <NotFound404/> } ] export default routes
import { NavLink, useRoutes } from 'react-router-dom' //.... import router from "./router"; function App() { const routersElement = useRoutes(router) return ( <div> <nav className={'top-nav'}> {/*链接*/} <NavLink to='/'>发现音乐</NavLink> <NavLink to='/myMusic'>我的音乐</NavLink> <NavLink to='/friends'>朋友</NavLink> <NavLink to='/download'>下载客户端</NavLink> </nav> <hr/> {routersElement} </div> ); }
Redux
是JavaScript
应用的状态容器,提供可预测的状态管理工具。
那么,我们为什么会需要这个工具?
就打个比方说,阎王看上了手下黑白无常的手下的能力,于是就找来了黑白无常并要求他们告诉他们的手下去办事。这样一来,就形成了一个上级去寻找中级,中级再去寻找低级的环。这样一来就过于麻烦,身为阎王,为什么不能直接找他们的手下办事。于是乎,就需要一个“生死薄”,通过“生死薄”来对任何人进行安排。
最终,就出现了 redux 这个管理工具。
// reducer.js const initState = { count: 0, list: ['春', '夏', '秋', '冬'], goodsInfo: { name: '一加8pro', price: 4888 } } const reducer = (state = initState, action) => { switch (action.type) { case 'increment': return {...state, count: state.count + action.step} case 'decrement': return {...state, count: state.count - action.step} case 'toString': return {...state, list: state.list.toString()} default: return state } } export default reducer
首先,创建一个reducer.js
的文件,并在其中定义一个初始的状态,以及一个 reducer
的函数。
其中两个参数分别代表为初始的状态,以及一个状态。
// action.js // action creator 生成action const handleIncrement = (step = 1) => { return { type: 'increment', step } } const handleDecrement = (step = 1) => { return { type: 'decrement', step } } const handleToString = () => { return { type: 'toString', } } export { handleIncrement, handleDecrement, handleToString }
之后再通过createStore
去创建一个store
import { createStore } from "redux";
import reducer from "./reducer";
const store = createStore(reducer)
export default store
这样一来,我们就能在页面中去使用state里面的数据。
import store1 from "../1-redux/store"; import { handleIncrement, handleDecrement, handleToString } from "../1-redux/action" const Redux1Page = () => { console.log('store', store); console.log('store->state', store.getState()); const {count, list, goodsInfo} = store.getState() const {dispatch} = store return ( <> <h2>Redux1Page</h2> <h2> <b>count: {count}</b><br/> <b>list: {list}</b><br/> <b>goodsInfo-name: {goodsInfo.name}</b><br/> <b>goodsInfo-price: {goodsInfo.price}</b><br/> </h2> <button onClick={()=>{ dispatch(handleIncrement(4)) }}>increment</button><br/> <button onClick={()=>{ dispatch(handleDecrement(4)) }}>increment</button><br/> <button onClick={()=>{ dispatch(handleToString()) }}>increment</button> </> ) } export default Redux1Page
将store
的内容打印出来后,我们能看到store内蕴含的方法,并通过 store.getState()
获取到state
里面的数据。
通过createStore
,将reducer的内容变成一个存储这些数据的一个仓库,并把reducer
的数据全部存储在这个仓库内。
进而通过store
内的方法(派发dispatch
)调用 handleIncrement, handleDecrement, handleToString
这些方法,通过方法里面的type
以及传进去的参数step
进行数据的更新。
但此时,我们点击按钮时,并未发生点击更新的状态,那,我们该如何进行数据的更新。
我们在入口文件index.js
中,通过上方store
的方法,来注册一个监听器,实时监听数据并渲染。
store.subscribe(()=>{
root.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>
)
})
再回头点击操作时,发现已经可以正常使用了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。