React入门及18及Next.js


一、React是什么?

  • React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库
  • 使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作组件

1. JSX

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 执行更快,因为它在编译为 JavaScript 代码后进行了优化
  • 它是类型安全的,在编译过程中就能发现错误
  • 使用 JSX 编写模板更加简单快速

2. 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。


二、React18

1. 下载包

命令:
yarn add react@next react-dom@next
yarn add @babel/standalone

2. 组件

类式组件

在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

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文件,此时,项目中reactreact-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是什么?Hook 它是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
总之,Hook 是一个特殊的函数.

1. 批处理

在说这个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中批处理。

2. 18新特性 自动批处理

从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 }));
  });
};

3. useState

// 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

在上述代码中:

  • 引入 React 中的 useState Hook。它让我们在函数组件中存储内部 state。
  • 在UseState组件内部,我们通过调用 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

4. useEffect

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

请添加图片描述


通过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代码,并渲染到页面之中。

总体演示效果:
在这里插入图片描述


5. useLayoutEffect

我们比较常用的是useEffect这个hook,当然在官方文档中,当它出问题的时候再尝试使用useLayoutEffect,那么useLayoutEffectuseEffect的区别是什么呢?

  • 首先,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刚好与之相反。


6. useContext

在我们爷孙之间传值的时候,如果使用两层父子更新是不是比较麻烦。于是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

在这里插入图片描述


7. useReducer

在某些场景下,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个参数。

  • 第一个参数:reducer函数,用来接收当前项目的state和将要触发的动作action,从而计算并返回计算出来的state。
  • 第二个参数:初始化的state。返回值为最新的state和dispatch函数,也就是用来触发reducer函数的,从而进行更新值的变化。

在这里插入图片描述


8. useMemo

把“创建”函数和依赖项数组作为参数传入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值发生变化的时候,才会进行计算。反之则会在每次渲染时都会计算新的值。

在这里插入图片描述


9. useCallback

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

任何一个输入框的变化,在这里插入图片描述
都仅会导致自己的组件重新渲染。

10. useImperativeHandle

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}/> 元素。
在这里插入图片描述


五、React-router-dom v6

1. 说在前头

为了快速使用路由,请先确保您的环境处于:
node.js版本 >= 12.x.x
npm或者yarn包管理工具
JavaScript,React.js和React Hooks的基础知识

2. 使用步骤

2.1 引入库

首先,进入到整个项目的入口文件index.js文件内,然后在这个文件内进行引入。

// index.js
import {
    BrowserRouter as Router
} from 'react-router-dom'

同时,也需要将<App/>包裹起来,就像这样:

// index.js
root.render(
    <Router>
        <App/>
    </Router>
)

2.2 NavLink

<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>

在这里插入图片描述

2.3 嵌套路由

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中,indexpath属性是不可得兼的,而且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组件,告知子路由,将内容渲染至什么位置。

2.4 重定向

Navigate
代替了Redirect组件,当在某个路径/a下,要重定向到路径/b时,就可以通过Navigate组件进行重定向

import {
    Navigate
} from 'react-router-dom'
const RankPage = () => {
    return (
        <>
            <Navigate to='/download'/>
            <h2>RankPage</h2>
        </>
    )
}
export default RankPage

当我们来到排行榜页面时,会重定向到下载页面。
在这里插入图片描述

2.5 useSearchParams

获取查询参数。
使用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

在这里插入图片描述

2.6 useParams

在组件内通过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

在这里插入图片描述

2.7 useNavigate

使用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

在这里插入图片描述

3. 模块化 useRoutes

// 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

ReduxJavaScript 应用的状态容器,提供可预测的状态管理工具。

那么,我们为什么会需要这个工具?
就打个比方说,阎王看上了手下黑白无常的手下的能力,于是就找来了黑白无常并要求他们告诉他们的手下去办事。这样一来,就形成了一个上级去寻找中级,中级再去寻找低级的环。这样一来就过于麻烦,身为阎王,为什么不能直接找他们的手下办事。于是乎,就需要一个“生死薄”,通过“生死薄”来对任何人进行安排。
最终,就出现了 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>
  )
})

再回头点击操作时,发现已经可以正常使用了。

to be continue 更新日期2022.6.15

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值