分类
react

React mobx Store 分拆

一个较大的项目如果所有内容都写在一个store里,就显得太多。将一个store拆散,分为一个主store和多个副store。副store可以用到主store的数据。主store有副store的引用便于提供React.createContext。和保持只有一个Provider包裹在主UI组件中

创建主Store

// store.ts
import PageEffectStore from '../Home/stores/PageEffectStore'
import HybridStore from './stores/HybridStore'

class Store {
  /** 副store1 */
  pageEffectStore: PageEffectStore
  /** 副store2 */
  hybridStore: HybridStore
  pageData = ''
  constructor() {
    this.hybridStore = new HybridStore({ mainStore: this })
    this.pageEffectStore = new PageEffectStore({ mainStore: this })
    makeAutoObservable(this)
  }
}

创建副Store

// hybridStore
interface HybridStoreConstructor {
  mainStore: MainStore
}

class HybridStore {
  mainStore: MainStore
  constructor({ mainStore }: HybridStoreConstructor) {
    this.mainStore = mainStore
    makeAutoObservable(this)
  }
}
分类
react

React mobx共享全局变量

mobx持久性变量实现方式。会有一下几个步骤:1.定义store。2.暴露store。3.引入在.tsx引入store。4.在.tsx内使用store

定义Store

// store.ts
import { makeAutoObservable } from 'mobx'

class Store {
  visible = false
  constructor() {
    makeAutoObservable(this)
  }
}

export default Store

暴露Store

// context.ts
import React from 'react'
import Store from './store'

const GlobalContext = React.createContext<Store>(new Store())

export default GlobalContext

引入Store

// index.tsx
import React, { FC } from 'react'
import Store from './store'
import GlobalContext from './context'
import { observer, useLocalObservable } from 'mobx-react'

const Home:FC = () => {
  const store = useLocalObservable(() => new Store())
  return (<GlobalContext.Provider value={store}>...</GlobalContext.Provider>)
}

export default observer(Home)

使用Store

import GlobalContext from '../context'
import React, { FC, useContext } from 'react'

const LongPage:FC<...> = () => {
  const globalStore = useContext(GlobalContext)
}
export default observer(LongPage)

分类
javascript react

React函数式编程使用lodash的防抖

import React, { FC, useRef } from 'react'
import copy from 'copy-to-clipboard'
import { message } from 'hljd'
import { debounce } from 'lodash'
interface ColorPickerInputNativeProps {
  value?: string
  onChange?: (value: string) => void
}
const ColorPickerInputNative: FC<ColorPickerInputNativeProps> = ({ value, onChange }) => {
  const eRef = useRef('')
  const funcRef = useRef(
    debounce(() => {
      copy(eRef.current)
      onChange?.(eRef.current)
      message.success('颜色值已复制')
    }, 200)
  )
  const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
    eRef.current = e.target.value
    funcRef.current()
  }
  return <input type="color" value={value} onChange={handleChange} />
}
export { ColorPickerInputNative }

要点:

  • 改变的值存入useRef中
  • 在useRef中放置debounce
分类
react

hooks使用心得

在React 函数式编程中写定时器

let [count, setCount] = useState(0)
    let intervalCb = useRef(null)
    let CountTimer
    useEffect(() => {
        intervalCb.current = () => {
            setCount(count+1)   
        }
    }, [count])

    useEffect(() => {
        function itvFn() {
            intervalCb.current()
        }
        CountTimer = window.setInterval(itvFn, 1000)
        return () => window.clearInterval(CountTimer)
    }, [])

const handleStop = () => { // 暂停
        window.clearInterval(CountTimer)
    }

如何获取一次数据

import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
  const [data, setData] = useState({ hits: [] });
 
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux',
      );
 
      setData(result.data);
    };
 
    fetchData();
  }, []);
 
  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}
 
export default App;

useEffect尽量不要依赖不影响更新的依赖项

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );
 
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(url);
 
      setData(result.data);
    };
 
    fetchData();
  }, [url]);
 
  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>
 
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

将获取数据,loading,error的逻辑变为自定义钩子

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
 
const useDataApi = (initialUrl, initialData) => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
 
  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
 
      try {
        const result = await axios(url);
 
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }
 
      setIsLoading(false);
    };
 
    fetchData();
  }, [url]);
 
  return [{ data, isLoading, isError }, setUrl];
};
 
function App() {
  const [query, setQuery] = useState('redux');
  const [{ data, isLoading, isError }, doFetch] = useDataApi(
    'https://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
  );
 
  return (
    <Fragment>
      <form
        onSubmit={event => {
          doFetch(
            `http://hn.algolia.com/api/v1/search?query=${query}`,
          );
 
          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>
 
      {isError && <div>Something went wrong ...</div>}
 
      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}
 
export default App;

useState的setState

setState的第一个参数可以传入函数,函数中的第一个参数是state的值

createRef 与 useRef 的区别

– createRef 每次渲染都会返回一个新的引用 – useRef 每次都会返回相同的引用

useMemo缓存组件

const child1 = useMemo(() => <Child1 count={count} />, [count])
分类
react

mobx

定义

import { observable } from "mobx";

class Todo {
    id = Math.random();
    @observable title = "";
    @observable finished = false;
}
import { decorate, observable } from "mobx";

class Todo {
    id = Math.random();
    title = "";
    finished = false;
}
decorate(Todo, {
    title: observable,
    finished: observable
})

经过计算的属性

class TodoList {
    @observable todos = [];
    @computed get unfinishedTodoCount() {
        return this.todos.filter(todo => !todo.finished).length;
    }
}
分类
react

react-router-dom

最基本的路由

  • 在Route中加入exact确保精准匹配,用在主页面
  • 在Route下放置组件
import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";

export default function BasicExample() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/dashboard">Dashboard</Link>
          </li>
        </ul>

        <hr />
        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

// You can think of these components as "pages"
// in your app.

function Home() {
  return (
    <div>
      <h2>Home</h2>
    </div>
  );
}

function About() {
  return (
    <div>
      <h2>About</h2>
    </div>
  );
}

function Dashboard() {
  return (
    <div>
      <h2>Dashboard</h2>
    </div>
  );
}

如何使用路由参数

  • 首先引入Router包在外面(import { Router } from ‘react-router-dom’)
  • 引入跳转功能放在Router里面(import { Link } from ‘react-router-dom’)
  • 引入Switch加上切换页面的功能(import { Switch } from ‘react-router-dom’)
  • 引入Route,switch的子节点。(import { Route } from ‘react-router-dom’)
  • 在Route中定义路由参数和children
  • children的函数式组件可以使用useParams()获取路由参数对象
import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link,
  useParams
} from "react-router-dom";

export default function ParamsExample() {
  return (
    <Router>
      <div>
        <h2>Accounts</h2>

        <ul>
          <li>
            <Link to="/netflix">Netflix</Link>
          </li>
          <li>
            <Link to="/zillow-group">Zillow Group</Link>
          </li>
          <li>
            <Link to="/yahoo">Yahoo</Link>
          </li>
          <li>
            <Link to="/modus-create">Modus Create</Link>
          </li>
        </ul>

        <Switch>
          <Route path="/:id" children={<Child />} />
        </Switch>
      </div>
    </Router>
  );
}

function Child() {
  // We can use the `useParams` hook here to access
  // the dynamic pieces of the URL.
  let { id } = useParams();

  return (
    <div>
      <h3>ID: {id}</h3>
    </div>
  );
}

嵌套的路由

  • 主要使用了useRouteMatch这个hook,这和匹配Route的原理是一样的
  • 从useRouteMatch从取得path和url,url是真实的跳转路径,path大多数时候和url一样,但是会有/path/:id这样的情况可用在Route里
import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link,
  useParams,
  useRouteMatch
} from "react-router-dom";

export default function NestingExample() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/topics">Topics</Link>
          </li>
        </ul>

        <hr />

        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/topics">
            <Topics />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

function Home() {
  return (
    <div>
      <h2>Home</h2>
    </div>
  );
}

function Topics() {
  let { path, url } = useRouteMatch();

  return (
    <div>
      <h2>Topics</h2>
      <ul>
        <li>
          <Link to={`${url}/rendering`}>Rendering with React</Link>
        </li>
        <li>
          <Link to={`${url}/components`}>Components</Link>
        </li>
        <li>
          <Link to={`${url}/props-v-state`}>Props v. State</Link>
        </li>
      </ul>

      <Switch>
        <Route exact path={path}>
          <h3>Please select a topic.</h3>
        </Route>
        <Route path={`${path}/:topicId`}>
          <Topic />
        </Route>
      </Switch>
    </div>
  );
}

function Topic() {
  let { topicId } = useParams();

  return (
    <div>
      <h3>{topicId}</h3>
    </div>
  );
}

有权限的路由

  • 在Link中我们有一个需要权限的路由跳转
  • 该需要权限的路由是获取useContext里的数据表示用户是否有权限
  • 有权限把children渲染进去,无权限重定向至/login

https://reactrouter.com/web/example/auth-workflow

匹配的路由给与修改

  • 不直接定义Link,给Link套一层
  • 在Link外层加入参数
  • 在函数内使用useRouterMatch(带参数)去匹配路由,如果匹配上添加一点样式

https://reactrouter.com/web/example/custom-link

当表单里有内容阻止页面离开

  • 需要一个数据来知道表单是否被修改过
  • 使用Prompt组件阻止离开

https://reactrouter.com/web/example/preventing-transitions

不匹配地址

  • 核心理念就是加入一个匹配任何页面的Route
  • 定义Route下的函数
  • 可以使用useLocation获取到想要到达的地址

https://reactrouter.com/web/example/no-match