React 之 专题(React Hooks、Fomik、CSS-IN-JS、Chakra-UI )

React 之 专题(React Hooks)

1、React Hooks 介绍

1.1 React Hooks 是用来做什么的

对函数型组件进行增强, 让函数型组件可以存储状态, 可以拥有处理副作用的能力.

让开发者在不使用类组件的情况下, 实现相同的功能.

1.2 类组件的不足 (Hooks 要解决的问题)

  1. 缺少逻辑复用机制
  • 为了复用逻辑增加无实际渲染效果的组件,增加了组件层级 代码显示十分臃肿
  • 增加了调试的难度以及运行效率的降低
  1. 类组件经常会变得很复杂难以维护
  • 将一组相干的业务逻辑拆分到了多个生命周期函数中 (与Vue2.0相似,Vue3.0就增加了Composition API (组合式 API)来解决此问题)
  • 在一个生命周期函数内存在多个不相干的业务逻辑
  1. 类成员方法不能保证this指向的正确性
    经常要使用 bing 或者 嵌套函数来保证,使得代码看起来复杂;

2、 React Hooks 使用

Hooks 意为钩子, React Hooks 就是一堆钩子函数, React 通过这些钩子函数对函数型组件进行增强, 不同的钩子函数提供了不同的功能.

  • useState()
  • useEffects()
  • useReducer()
  • useRef()
  • useCallback()
  • useContext()
  • useMemo()

2.1 useState()

用于为函数组件引入状态

import React, { useState } from 'react';

function App () {
	const [count, setCount ] = useState(0);
	return <div>
		<span>{count}</span>
		<button onClick={() => setCount(count + 1)}> +1 </button>
	</div>;
}

使用说明:

  1. 接收唯一的参数即状态初始值. 初始值可以是任意数据类型.
  2. 返回值为数组. 数组中存储状态值和更改状态值的方法. 方法名称约定以set开头, 后面加上状态名称.
  3. 方法可以被调用多次. 用以保存不同状态值.
  4. 参数可以是一个函数, 函数返回什么, 初始状态就是什么, 函数只会被调用一次, 用在初始值是动态值的情况.

设置状态值方法的参数可以是一个值也可以是一个函数.
设置状态值方法的方法本身是异步的.

import React, { useState } from 'react';

function App () {
  const [ count, setCount] = useState (0)
  const [ person, setPerson] = useState ({ name: "张三", age: 20})
	function handlecount() {
	  setCount (count => {
	  	return count + 1
	  })
	}
	document.title= count
  return  <div>
	<span>{count} {person.name} {person.age}</span>   
	<button onClick={handlecount}>+ 1</button>
	<button onclick={ () => setPerson({...person, name:"李四"}) }> setperson </button>
  </div>
}

2.2 useReducer()

useReducer是另一种让函数组件保存状态的方式.

import React, { useReducer } from  'react';


function App () {
    function reducer  (state, action) (
	  switch (action.type) {
	    case 'increment':
		  return state + 1
	  }
	}
	const [ count, dispatch ] = useReducer(reducer, 0)
	return  <div>
		<span>{ count }</span>
		<button onClick={ () => dispatch({ type: 'increment' })}> +1</button>
	</div>
}

2.3 useContext()

在跨组件层级获取数据时简化获取数据的代码.

import { createContext, useContext } from 'react';
const countcontext = createContext();
function App() {
  return (
    <countcontext.Provider value={100}>
      <Toolbar />
    </countcontext.Provider>
  );
}

function Toolbar() {
  const value = useContext(countcontext);  
  return <div>{value}</div>;
}

2.4 useEffect()

让函数型组件拥有处理副作用的能力. 类似生命周期函数.

  1. useEffect 执行时机
    可以把 useEffect 看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合. 有三种情况
  • useEffect(() => {}) => componentDidMount, componentDidUpdate 组件挂载完成执行一次 组件数据更新完成之后执行
  • useEffect(() => {}, []) => componentDidMount 组件挂载完成执行一次
  • useEffect(() => () => {}) => componentWillUnMount 组件被卸载之前执行
  1. useEffect 使用方法

为window对象添加滚动事件,组件卸载时移除
设置定时器让count数值每隔一秒增加1 ,组件卸载时清除定时器

  1. useEffect 解决的问题
  • 按照用途将代码进行分类 (将一组相干的业务逻辑归置到了同一个副作用函数中)
  • 简化重复代码, 使组件内部代码更加清晰
import { useEffect } from "react";
import ReactDOM from 'react-dom';

function Api (props) {  

  function scroller() {
    console.log('滚动了')
  }
  useEffect(() => {
    window.addEventListener('scroll', scroller)
    return () => {
      window.removeEventListener('scroll', scroller)
    }
  }, [])
  return <div>
    <button onClick={ () => ReactDOM.unmountComponentAtNode(document.getElementById('root')) }>卸载组件</button>
  </div>
}
  1. 只有指定数据发生变化时触发effect
useEffect(() => {
  document.title = count
}, [count])
  1. useEffect 结合异步函数

useEffect中的参数函数不能是异步函数, 因为useEffect函数要返回清理资源的函数, 如果是异步函数就变成了返回Promise

useEffect(() => {
  (async () => {
      await axios.get()
  })()
})

2.5 useMemo()

useMemo 的行为类似Vue中的计算属性, 可以监测某个值的变化, 根据变化值计算新值.

useMemo 会缓存计算结果. 如果监测值没有发生变化, 即使组件重新渲染, 也不会重新计算. 此行为可以有助于避免在每个渲染上进行昂贵的计算.

import { useMemo } from "react";

const result = useMemo(() => {
  return count *2
}, [count])

2.6 memo 方法 (性能优化)

性能优化, 如果本组件中的数据没有发生变化, 阻止组件更新. 类似类组件中的 PureComponent 和 shouldComponentUpdate

会返回一个新的组件

import { memo } from "react";

const Counter = memo(function Count(){
  return <div></div>
})

export default Counter;

2.7 useCallback() 方法 (性能优化)

性能优化, 缓存函数, 使组件重新渲染时得到相同的函数实例. 防止组件因为函数实例不同而重新渲染

import { memo, useCallback, useState } from "react";

function Api () {
  const [count, setCount] = useState(0);
  const resetCount = useCallback( () => setCount(0), [setCount])
  return <div>
    <div>{count}</div>
    <button onClick={ () => setCount(count+1) }> +1</button>
    <Test resetCount={resetCount}/>
  </div>
}

const Test = memo(function Test1(props){
  console.log('组件渲染了')
  return <div>
    Test
    <button onClick={ props.resetCount }> reset </button>
  </div>
})  

2.8 useRef()

  1. 获取DOM元素对象
import { useRef } from "react";
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <div>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </div>
  );
}
  1. 保存数据 (跨组件周期)

即使组件重新渲染, 保存的数据仍然还在. useFef保存的数据被更改不会触发组件重新渲染. 举例:可以用于清除定时器

function App() {
  let timer = useRef();
  useEffect(() => {
    timer = setInterval(() => {
      setCount(count + 1)
    },1000)
  })
  const stopCount = () => {
    clearInterval(timer)
  }
  return <div></div>
}

3. 自定义 Hook

  • 自定义 Hook 是标准的封装和共享逻辑的方式.
  • 自定义 Hook 是一个函数, 其名称以 use 开头.
  • 自定义 Hook 其实就是逻辑和内置 Hook 的组合.

主要目的就为了实现,组件之间的数据共享

import { useState, useEffect } from 'react';
// 自定义Hook
function useFriendStatus() {  
  const [list, setList] = useState({});
  useEffect(() => {
    axios.get('http://xxx').then(res => setList(res.data))
  }, []);
  return [list, setList];
}
function App() {  
  const [list, setList] = useStatus();
  return <div> {list} </div>
}

4. React 路由 Hooks

react-router-dom 路由提供的钩子函数

import { 
  useHistory, 
  useLocation,
  useRourteMatch,
  useParams
} from 'react-router-dom'
// 获取对应的路由对象

5.useState 钩子函数简单实现

import React from 'react';
import ReactDOM from 'react-dom';

let state = [];
let setters = [];
let stateIndex = 0;

function createSetter (index) {
  return function (newState) {
    state[index] = newState;
    render ();
  }
}

function useState (initialState) {
  state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState;
  setters.push(createSetter(stateIndex));
  let value = state[stateIndex];
  let setter = setters[stateIndex];
  stateIndex++;
  return [value, setter];
}

function render () {
  stateIndex = 0;
  effectIndex = 0;
  ReactDOM.render(<App />, document.getElementById('root'));
}
function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Tom');
  return <div>
    {count}
    <button onClick={ () => setCount(count + 1) }>+1</button>
    <button onClick={ () => setName('JDAK')}>名字</button>
  </div>;
}

6.useEffect 钩子函数简单实现

function render () {
  effectIndex = 0;
  ReactDOM.render(<App />, document.getElementById('root'));
}


let prevDepsAry = [];
let effectIndex = 0;

function useEffect(callback, depsAry) {
  // 判断callback是不是函数
  if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect函数的第一个参数必须是函数');
  // 判断depsAry有没有被传递
  if (typeof depsAry === 'undefined') {
    // 没有传递
    callback();
  } else {
    // 判断depsAry是不是数组
    if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect函数的第二个参数必须是数组');
    // 获取上一次的状态值
    let prevDeps = prevDepsAry[effectIndex];
    // 将当前的依赖值和上一次的依赖值做对比 如果有变化 调用callback
    let hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true;
    // 判断值是否有变化
    if (hasChanged) {
      callback();
    }
    // 同步依赖值
    prevDepsAry[effectIndex] = depsAry;
    effectIndex++;
  }
}

7. useReducer 钩子函数简单实现

function useReducer (reducer, initialState) {
  const [state, setState] = useState(initialState);
  function dispatch (action) {
    const newState = reducer(state, action);
    setState(newState);
  }
  return [state, dispatch];
}

function App() {
  function reducer (state, action) {
    switch (action.type) {
      case 'increment':
        return state + 1;
      case 'decrement':
        return state - 1;
      default:
        return state;
    }
  }
  const [count, dispatch] = useReducer(reducer, 0);
  return <div>
    {count}
    <button onClick={() => dispatch({type: 'increment'})}>+1</button>
    <button onClick={() => dispatch({type: 'decrement'})}>-1</button>
  </div>;
}

React 之 Fomik

1.1 Formik 介绍

增强表单处理能力. 简化表单处理流程.
https://jaredpalmer.com/formik/

2 Formik 基本使用

官网:
https://github.com/jquense/yup#readme

下载

npm install formik

2.1 使用 formik 进行表单数据绑定以及表单提交处理.

import { useFormik } from 'formik';

function App () {
  const formik = useFormik ({initialvalues: { username: '张爽'}, onsubmit: values => {}});
  return <form onSubmit={formik.handleSubmit}>
    <input type="text" name="username"
      value={formik.values.username}
      onchange={formik.handleChange}/>
    <input type="submit"/>
  </form>
}

2.2 表单验证

function App () {
  const formik = useFormik ({
    validate: values => {
      const errors = {};
      if(!values.username) errors.username = '请用户输死';
      return errors;
    }, 
    onSubmit: values => {}
  });
  return <form onSubmit={formik.handleSubmit}>
    <div>{formik.touched.username && formik.errors.username ? formik.errors.username : ''}</div>
  </form>
}
// formik.handleBlur 离开焦点时触发验证
// onBlur={formik.handleBlur}
// formik.touched.username 判断表单元素是否被改动过

2.3 使用 yup 配合formik 表单验证

下载yup
npm install yup
使用

import { useFormik } from "formik";
import * as Yup from "yup";

const formik = useFormik ({
    initialvalues: { username: '张爽', password: 1234}, 
    validationSchema: Yup.object({
	    username: Yup.string()
	      .max(15, "用户名的长度不能大于15")
	      .required("请输入用户名"),
	   	password: Yup.string()
	   	  .max(8, "密码。。。")
	   	  .required("请输入密码")
	}),
    onSubmit: values => {}
});

2.4 getFieldProps 方法

减少样板代码

{...formik.getFieldProps('username')} 
可以代替
onchange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.username}

2.5 使用组件的方式构建表单

组价

import { Formik, Form, Field, ErrorMessage, useField } from "formik";
import * as Yup from "yup";

function App() {
  const initialValues = {username: '', hobbies: ['足球', '篮球']};
  const handleSubmit = (values) => {
    console.log(values);
  };
  const schema = Yup.object({
    username: Yup.string()
      .max(15, "用户名的长度不能大于15")
      .required("请输入用户名"),
  });
  return (
    <Formik
      initialValues={initialValues}
      onSubmit={handleSubmit}
      validationSchema={schema}
    >
      <Form>
        <Field name="username" /> //默认是输入框
        <ErrorMessage name="username" />
        <Checkbox value="足球" label="足球" name="hobbies"/>
        <button type="submit">提交</button>
      </Form>
    </Formik>
  );
}

2.6 构建其他表单项

默认情况下, Field组件渲染的是文本框. 如要生成其他表单元素可以使用以下语法.

<Field name="username" as="textarea" /> // 文本域
<Field name="subject" as="select"> // 下拉框
	<option value="1">123</option>
	<option value="2">234</option>
	<option value="3">345</option>
</Field>

2.7 使用 useField 构建 自定义表单控件

import { useField } from "formik";

function myInput({label, ...props}) {
	const [field, meta] = useField(props)
	return <div>
		<lable htmlFor={props.id}>{ label }</label>
		<input {...field} {...props}/>
		{meta.touched && meta.error ? <div>meta.error</div> : null}
	</div>
}
<myInput id="user" label="密码" type="text" name="username" placeholder="请输入用户名"/>

受控组件与非受控组件

非受控组件

表单数据交由DOM节点管理. 特点是表单数据在需要时进行获取. 代码实现相对简单.

受控组件

表单数据交由state对象管理. 特点是可以实时得到表单数据. 代码相对复杂.

推荐使用受控组件

CSS-IN-JS

集成 CSS 代码在 JavaScript 文件

1. 为什么会有 CSS-IN-JS

CSS-IN-JS 是 WEB 项目中将 CSS 代码捆绑在 JavaScript 代码中的解决方案.

这种方案旨在解决 CSS 的局限性, 例如缺乏动态功能, 作用域和可移植性.
将css与js放在一起,方便组件移植,并且让其有自己的作用域,与VUE 中的 style 中的 scoped 作用一样。

2. CSS-IN-JS 方案的优缺点

优点:

  1. 让 CSS 代码拥有独立的作用域, 阻止 CSS 代码泄露到组件外部, 防止样式冲突.
  2. 让组件更具可移植性, 实现开箱即用, 轻松创建松耦合的应用程序
  3. 让组件更具可重用性, 只需编写一次即可, 可以在任何地方运行. 不仅可以在同一应用程序中重用组件, 而且可以在使 用相同框架构建的其他应用程序中重用组件.
  4. 让样式具有动态功能, 可以将复杂的逻辑应用于样式规则, 如果要创建需要动态功能的复杂UI, 它是理想的解决方案.

缺点:

  1. 为项目增加了额外的复杂性.
  2. 自动生成的选择器大大降低了代码的可读性

推荐使用CSS-IN-JS方案

3. Emotion 库

Emotion 是一个旨在使用 JavaScript 编写 CSS 样式的库. 实现 CSS-IN-JS 方案

3.1下载

npm install @emotion/core @emotion/styled

3.2使用方式

1、JSX Pragma

通知 babel, 不再需要将 jsx 语法转换为 React.createElement 方法, 而是需要转换为 jsx 方法.

在这里插入图片描述

/** @jsx jsx **/  //注释也要写
import { jsx } from '@emotion/core';

function App() {
	return <div css={{ width: 200, height: 200 }}>lalal</div>
}

2、 Babel Preset

  1. npm run eject 使用其弹窗 react 配置
  2. 在 package.json 文件中找到 babel 属性, 加入如下内容
"presets":[
	"react-app",
	"@emotion/babel-preset-css-prop"
]

之后便可在组件中直接使用css={}属性
推荐使用第二种方式

3.3 css方法

import { css } from '@emotion/core';
// 方式1 推荐
const style = css`
	width: 100px;
	background: skyblue;
`;
// 方式2
style = css({
	width: 100,
	background: 'skyblue'
})

<div css={style}> App </div>

3.4 css 属性优先级

props 对象中的 css 属性优先级高于组件内部的 css 属性.
在调用组件时可以在覆盖组件默认样式.

及外部优先级大于内部的

3.5 Styled Components 样式化组件

样式化组件就是用来构建用户界面的,是 emotion 库提供的另一种为元素添加样式的方式。

3.5.1 创建样式化组件

用法

import { styled } from '@emotion/styled';

const Conter = styled.div`  //也可使用对象形式
  width: 100px;
  height: 100px;
`;

function App() {
  return <div>
  	App works
  	<Conter/>
  </div>;
}
3.5.2 根据 props 属性覆盖样式
import { styled } from '@emotion/styled';

const Conter = styled.div`  //也可使用对象形式
  width: ${props => props.width || '100px'};
  height: 100px;
`;
// ----------------------
const Conter = styled.div(props => ({  //对象形式
  width: props && props.width || '100px',
  height: '100px'
)});


function App() {
  return <div>
  	App works
  	<Conter width={100}/>
  </div>;
}
3.5.3 为任何组件添加样式
import { styled } from '@emotion/styled';

function Demo ({className}) {
	return <div className={className}>Demo</div>
}

const Conter = styled(Demo)`
  width: 100px;
  height: 100px;
  color: red;
`;

function App() {
  return <div>
  	<Conter/>
  </div>;
}
3.5.4 通过父组件设置子组件样式
或者

如果不生效 (需要 babel 插件支持)
babel 插件配置

  1. npm install babel-plugin-emotion
  2. 在 package.json 文件中找到 babel 属性, 加入如下内容:
"babel": {
	"plugins": ["emotion"]
}
3.5.5 嵌套选择器 &

& 表示组件本身.

3.5.6 as 属性

要使用组件中的样式, 但要更改呈现的元素, 可以使用 as 属性.

as 可以改变组件的标签;

3.6 样式组合

在样式组合中, 后调用的样式优先级高于先调用的样式. 后覆盖前的;

3.7 全局样式

引入 Global 组件

3.8 关键帧动画

引入 keyframes 组件

import { css, keyframes } from '@emotion/core';

3.9 主题

  1. 下载主题模块
    npm install emotion-theming
  2. 使用
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// 引入 ThemeProvider 组件
import { ThemeProvider } from 'emotion-theming';
// 主题
const theme = {
  colors: {
    primary: 'tomato'
  }
};

ReactDOM.render(
  // 将 ThemeProvider 放置在视图在最外层
  <ThemeProvider theme={theme}><App /></ThemeProvider>,
  document.getElementById('root')
);
// app.js
import React from 'react';
import { css, keyframes } from '@emotion/core';
import { styled } from '@emotion/styled';
import { useTheme } from 'emotion-theming';
// 获取主题
const primaryColor = props => css`
  color: ${props.colors.primary}
`

function App() {
  console.log(useTheme()); // 获取主题

  return <div css={primaryColor}>
    App works
    <Conter/>
  </div>;
}

export default App;

Chakra-UI

现代化 React UI 框架 Chakra-UI

1. Chakra-UI 介绍

Chakra UI 是一个简单的, 模块化的易于理解的 UI 组件库. 提供了丰富的构建 React 应用所需的UI组件.
文档:
https://next.chakra-ui.com/docs/getting-started
https://chakra-ui.com/docs/getting-started

  1. Chakra UI 内置 Emotion,是 CSS-IN-JS 解决方案的集大成者
  2. 基于 Styled-Systems https://styled-system.com/
  3. 支持开箱即用的主题功能
  4. 默认支持白天和黑夜两种模式
  5. 拥有大量功能丰富且非常有用的组件
  6. 使响应式设计变得轻而易举
  7. 文档清晰而全面. 查找API更加容易
  8. 适用于构建用于展示的给用户的界面
  9. 框架正在变得越来越完善

2. Chakra-UI 开始

2.1 下载 chakra-ui

npm install @chakra-ui/core@1.0.0-next.2

2.2 克隆默认主题

Chakra-UI 提供的组件是建立在主题基础之上的, 只有先引入了主题组件才能够使用其他组件.
npm install @chakra-ui/theme

2.3使用

引入

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import { ChakraProvider, CSSReset } from "@chakra-ui/core"; // css重置组件
import theme from '@chakra-ui/theme'; // 主题

ReactDOM.render(
  <ChakraProvider theme={theme}>
    <CSSReset />
    <App />
  </ChakraProvider>,
  document.getElementById("root")
);

3. 主题

3.1 颜色模式(color mode)

chakra-ui 提供的组件都支持两种颜色模式, 浅色模式(light)和暗色模式(dark). 可以通过 useColorMode 进行颜色模式的更改.

Chakra 将颜色模式存储在 localStorage 中, 并使用类名策略来确保颜色模式是持久的.

3.2 根据颜色模式设置样式

chakra 允许在为元素设置样式时根据颜色模式产生不同值. 通过 useColorModeValue 钩子函数实现.

3.3 强制组件颜色模式

使组件不受颜色模式的影响, 始终保持在某个颜色模式下的样式.

3.4 颜色模式通用设置

  1. 设置默认颜色模式

通过 theme.config.initialColorMode 可以设置应用使用的默认主题.

  1. 使用操作系统所使用的颜色模式

通过 theme.config.useSystemColorMode 可以设置将应用的颜色模式设置为操作系统所使用的颜色模式.

3.5 主题对象

Colors

在设置颜色时, 可以但不限于取主题中提供的颜色值.

<Box color="red.500"></Box>
Space

使用 space 可以自定义项目间距. 这些间距值可以由 padding, margin 和 top, left, right, bottom 样式引用.

<Box mb="5"></Box> <!-- 5 => 1.25rem -->
Sizes

使用 size 可以自定义元素大小, 这些值可以由 width, height 和 maxWidth, minWidth 等样式引用.

<Box w="lg"></Box> <!-- lg => 32rem -->
Breakpoints

配置响应数组值中使用的默认断点. 这些值将用于生成移动优先(即最小宽度)的媒体查询.

// theme.js
import { createBreakpoints } from "@chakra-ui/theme-tools"

export default createBreakpoints({
  sm: "30em",
  md: "48em",
  lg: "62em",
  xl: "80em",
  "2xl": "96em",
})
<Box fontSize={["sm", "md", "lg", "xl"]}>Font Size</Box>

React Icons 推荐

官网:
http://react-icons.github.io/react-icons/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值