【React学习笔记】Hook&React Router

386 阅读7分钟

函数组件

接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素的组件被称为函数组件

注意点:

1. 函数组件里没有this和生命周期,不能使用string ref!!!(因为不存在this,无法使用this.refs)
2. 使用函数式组件时,应该尽量减少在函数中声明子函数,否则,组件每次更新时都会重新创建这个函数
(可使用useMemo来解决)
3. 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。
4. 不要在普通的 JavaScript 函数中调用 Hook, 只在 React 函数中调用 Hook。

Hook(钩子函数)

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

只有在函数式组件中能使用hook,在类式组件中不能使用hook?

常用hook

  • useState
import {useState} from 'react';

function App() {
  const [state, setState] = useState({
    name: 'xiaobai',
    age: 20
  })

  const changAge = function(){
    setState({
      name:state.name,//因为浅合并的特性,改变age同时也需要把其他属性带上!!!
      age:state.age+1
    })
  }

  return <div>
    <span>name:{state.name}</span>
    <br></br>
    <span>name:{state.age}</span>
    <button onClick={changAge}>点击改变年龄</button>
  </div>;
}

export default App;

另一点需要注意的是,useState 多次调用set方法不会自动合并!!!

  • useEffect

    该 Hook 接收一个包含命令式、且可能有副作用代码的函数

    副作用包括改变 DOM、添加订阅、设置定时器、记录日志等。

  1. 调用形式:

    useEffect(()=>{
        //副作用函数
        return ()=>{
        	//返还函数
    	};
    },[依赖参数])
    
  2. useEffect的执行过程大概是这样的:

    1. 挂载阶段:
       从上往下执行代码,执行的过程中,遇到useEffect,则将副作用函数推入一个专属队列。
       在组件挂载完成之后,会将所有的副作用函数执行,同时将所有的返还函数推入另一个专属队列。
       即:挂载->副作用函数
    2. 更新阶段:
       从上往下执行代码,执行的过程中,遇到useEffect,则将副作用函数推入一个专属队列。
       在组件挂载完成之后,先看返还函数专属队列是否为空,若不为空,则先执行所有的返还函数。
       之后再执行所有的副作用函数,同时将所有的返还函数推入另一个专属队列。
       即:挂载->返还函数->副作用函数
    3. 卸载阶段:
       执行返还函数队列中的所有的返还函数
       即:返还函数
    总的来说,useEffect可以基本上看成是componentDidMount,componentDidUpdate,componentWillUnmount的结合
    
  3. 依赖参数的不同取值的影响

    依赖参数:组件更新时,副作用和返还函数执行前会判断依赖参数是否更新。若更新,则执行;否则,不执行。
    1. [依赖参数]为空
       相当于依赖所有的参数,组件挂载、更新都会执行副作用函数。
    2. [依赖函数][]
       不依赖任何参数,只执行挂载阶段。
    3. 其他
       依赖给定参数数组中的所有参数,组件挂载或参数改变时会执行副作用函数。
    
  • useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

可以用来取得组件更新前的一些数据!!!

const [nub,setNub] = useState(0);
const prevNub = useRef(nub);
useEffect(()=>{
    console.log(nub,prevNub.current);//preNub.current可以用来记录nub上一次的值
    prevNub.current = nub;// 使用 useRef 记录数据时,该数据不会随着组件更新而自动更新
  },[nub])
  
return <div>
    <span>{nub}</span>
    <button onClick={()=>{
        setNub(nub+1);
      }}>nub递增</button>
</div>

  • useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo返回memorized值。把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。

也就是说只有当a,b改变时,才会执行() => computeExpensiveValue(a, b);其他值的改变都不会执行!(类似vue中的computed)

自定义hook

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。

与 React 组件不同的是,自定义 Hook 不需要具有特殊的标识。

每次调用 Hook,它都会获取独立的 state。

用自定义的hook来模拟实现sticky:

import { useEffect, useState } from "react";
function useScroll() {
   const [y,setY] = useState(window.scrollY);
   useEffect(()=>{
     window.onscroll = function() {
       setY(window.scrollY);
     } 
     return ()=>{
      window.onscroll = null;
     }
   },[])
   return [y,(newY)=>{
      window.scrollTo(0,newY);
      setY(newY);
   }]; 
}

export {useScroll};

测试效果所用代码:

import { useEffect, useRef } from "react";
import { useScroll } from "./useScroll.js";
import './index.css';
function App() {
  const [scrollY, setScrollY] = useScroll();
  let buttonEl = useRef();
  useEffect(() => {
    if (scrollY > window.innerHeight) {
      buttonEl.current.style.display = "block";
    } else {
      buttonEl.current.style.display = "none";
    }

  }, [scrollY])
  return <div className="test">
    <button ref={buttonEl} onClick={()=>{setScrollY(0)}}>{scrollY}</button>
  </div>
}

export default App;

css:

.test{
    height:4000px;
    background-color: bisque;
}

button{
    position: fixed;
    bottom: 0;
}

memo

1.调用 memo 方法,要传入一个组件,memo 会返回一个新的组件(高阶组件) 
2.调用 memo 返回的组件时,memo 内部会调用我们传入的组件
3.在 memo 父组件更新时,memo 会进行 prevProps 和 当前的 props 的浅对比,如果对比结果为 true 则不更新 子组件

简单使用举例:(data时传入child的数据,这样一个child更新不会导致其他child更新)

export default memo(Child,(props1,props2)=>{
    return props1.data === props2.data;
});

React Router

React Router提供了多种环境下的路由库:web、native

基于web的React Router为react-router-dom

基于native的React Router为react-router-native(www.npmjs.com/package/rea…)

( react-router、react-router-dom、react-router-native 关系: www.cnblogs.com/cag2050/p/9… )

react-router-dom

  • 下载:
npm i -S react-router-dom
  • BrowserRouter组件和HashRouter组件

    BrowserRouter组件--基于HTML5 History API的路由组件(不带#号)

    HashRouter组件--基于URL Hash的路由组件(带#号)

    index.js

    ReactDOM.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>,
    document.getElementById('root')
    };
    
  • route路由组件

    • path

      1. path默认模糊匹配,匹配开头,如path='/',则能匹配到以'/'开头的所有url,如'/index'等;

      2. 使用extract表示路由使用 精确匹配模式,如path='/about',加上extract只能匹配到'/about'和'/about/';

      3. 使用strict表示路由使用 严格匹配模式,如path='/about',加上strict只能匹配到'/about';注意,strict是基于extract的,如果没有extract, strict不起效果!!!

      4. 多路径匹配:数组 -> 如path={["/","/home","/index"]}

      5. path为空,则直接匹配成功

      6. 动态路由:如/list/:type?/:page?

    • Redirect组件:重定向

      to = 将 url 重新定义的结果
      
      from = 要进行重定向的 url,如果不写代表所有地址都进行重定向
      
    • component

      component是要显示的组件,使用component不能传递props!

      <Route path="/about/details" exact strict component={AboutDetailsPage} />
      
    • render

      使用render能向组件中传递参数!
      
      <Route path="/about/details" exact strict render={(routerProps)=>AboutDetailsPage} />
      
      路由参数(上面的routerProps):被 Route 调用的组件称之为路由组件,
      Route 在调用该组件时,会将路由相关的信息通过参数的形式,传递给组件。
      
      history:历史记录信息
          length -(数字)历史记录堆栈中的条目数
          action - (字符串)当前动作(PUSH,REPLACE,或POP)
          push(path, [state]) -(功能)将新条目推入历史记录堆栈
          replace(path, [state]) -(函数)替换历史记录堆栈上的当前条目
          go(n)-(函数)通过n条目在历史记录堆栈中移动指针
          goBack() -(功能)等同于 go(-1)
          goForward() -(功能)等同于 go(1)
        location
          pathname - 当前的url
          search - 当前 url 中 seach
          hash - 当前 url 中hash
          state - replace 或 push 传递的信息
        match
          params - 匹配到的动态路由的值
          isExact - 是否可以精确匹配(不是指是否设置了extract!)
          path -(字符串)当前 Route 对应的 path
          url - URL中的被匹配到的部分
      
  • Link组件

    <Link to="/">首页</Link>
    
  • NavLink组件

    <Nav to="/" activeClassName="selected" activeStyle={{fontWeight:"bold"}} isActive={()=>{}}>首页</Nav>
    
    1. NavLink功能与Link一致,但多了当前选中项
    2. 使用NavLink组件时,NavLink会根据当前url及自身的to属性去做匹配,匹配成功则给当前项加上选中的class
    3. activeClassName:匹配成功之后,要添加的class名,默认为 active
    4. activeStyle:匹配成功之后,要显示的style
    5. isActive() 判断当前是否应该选中, 返回值 true 选中,false 不选中
  • Switch组件

    其中一个 Route 匹配成功的话则不继续匹配 (不使用Switch的话Route匹配成功的话还会继续匹配)

  • 其他:非路由组件如何获取路由参数

    1. 使用高阶组件 -- withRouter(高阶路由)
    2. hooks -- useHistory、useLocation、useParams、useRouteMatch
  • 其他:图片显示问题

    1. 使用import
    import {img} from '../imgs/img.png'
    
    <img src={img}/>
    
    • 使用require
    <img src={require('../imgs/img.png').default}/>
    
  • 示例

const types = ["good","share","ask"];
function App() {
  const user = "小明";
  return (
    <div>
      <Nav />
      <Switch>
        <Route path={["/","/home","/index"]} exact render={(routerProps)=>{
          return <IndexPage user={user} {...routerProps} />
        }} />
        <Route path="/about" component={AboutPage} />
        <Route path="/join" exact component={JoinPage} />
        <Route path="/list/:type?/:page?" exact render={(routerProps)=>{
            const {params} = routerProps.match;
            const {type="good",page="1"} = params;
            if(types.includes(type)
            && parseInt(page) + "" === page
            && page > 0){
              return <ListPage />
            }
            return <Redirect to="/404" />;
        }} />
        <Route path="/404" exact component={UndefinedPage} />
        {/* 其他:跳转到404 */}
        <Redirect to="/404" />
      </Switch>
    </div>
  );
}

Nav组件:

function Nav() {
  return <nav className="nav">
      <NavLink 
        to="/" 
        activeClassName="selected"
        activeStyle={{
          fontWeight: "bold",
          textDecoration: "underline"
        }}
        isActive={(match,location)=>{
          const {pathname} = location;
          return pathname==="/"||pathname==="/index"||pathname==="/home";
        }}
      >首页</NavLink>
      <span> | </span>
      <NavLink 
        to="/about" 
        exact 
        activeClassName="selected"
        activeStyle={{
          fontWeight: "bold",
          textDecoration: "underline"
        }}
      >关于</NavLink>
      <span> | </span>
      <NavLink 
        to="/join" 
        exact 
        activeClassName="selected"
        activeStyle={{
          fontWeight: "bold",
          textDecoration: "underline"
        }}
      >加入</NavLink>
      <span> | </span>
      <NavLink 
        to="/list" 
        activeClassName="selected"
        activeStyle={{
          fontWeight: "bold",
          textDecoration: "underline"
        }}
      >列表</NavLink>
      <hr />
  </nav>
}