React 之 专题(React Hooks)
1、React Hooks 介绍
1.1 React Hooks 是用来做什么的
对函数型组件进行增强, 让函数型组件可以存储状态, 可以拥有处理副作用的能力.
让开发者在不使用类组件的情况下, 实现相同的功能.
1.2 类组件的不足 (Hooks 要解决的问题)
- 缺少逻辑复用机制
- 为了复用逻辑增加无实际渲染效果的组件,增加了组件层级 代码显示十分臃肿
- 增加了调试的难度以及运行效率的降低
- 类组件经常会变得很复杂难以维护
- 将一组相干的业务逻辑拆分到了多个生命周期函数中 (与Vue2.0相似,Vue3.0就增加了Composition API (组合式 API)来解决此问题)
- 在一个生命周期函数内存在多个不相干的业务逻辑
- 类成员方法不能保证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>;
}
使用说明:
- 接收唯一的参数即状态初始值. 初始值可以是任意数据类型.
- 返回值为数组. 数组中存储状态值和更改状态值的方法. 方法名称约定以set开头, 后面加上状态名称.
- 方法可以被调用多次. 用以保存不同状态值.
- 参数可以是一个函数, 函数返回什么, 初始状态就是什么, 函数只会被调用一次, 用在初始值是动态值的情况.
设置状态值方法的参数可以是一个值也可以是一个函数.
设置状态值方法的方法本身是异步的.
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()
让函数型组件拥有处理副作用的能力. 类似生命周期函数.
- useEffect 执行时机
可以把 useEffect 看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合. 有三种情况
- useEffect(() => {}) => componentDidMount, componentDidUpdate 组件挂载完成执行一次 组件数据更新完成之后执行
- useEffect(() => {}, []) => componentDidMount 组件挂载完成执行一次
- useEffect(() => () => {}) => componentWillUnMount 组件被卸载之前执行
- useEffect 使用方法
为window对象添加滚动事件,组件卸载时移除
设置定时器让count数值每隔一秒增加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>
}
- 只有指定数据发生变化时触发effect
useEffect(() => {
document.title = count
}, [count])
- 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()
- 获取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>
);
}
- 保存数据 (跨组件周期)
即使组件重新渲染, 保存的数据仍然还在. 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 方案的优缺点
优点:
- 让 CSS 代码拥有独立的作用域, 阻止 CSS 代码泄露到组件外部, 防止样式冲突.
- 让组件更具可移植性, 实现开箱即用, 轻松创建松耦合的应用程序
- 让组件更具可重用性, 只需编写一次即可, 可以在任何地方运行. 不仅可以在同一应用程序中重用组件, 而且可以在使 用相同框架构建的其他应用程序中重用组件.
- 让样式具有动态功能, 可以将复杂的逻辑应用于样式规则, 如果要创建需要动态功能的复杂UI, 它是理想的解决方案.
缺点:
- 为项目增加了额外的复杂性.
- 自动生成的选择器大大降低了代码的可读性
推荐使用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
- npm run eject 使用其弹窗 react 配置
- 在 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 插件配置
- npm install babel-plugin-emotion
- 在 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 主题
- 下载主题模块
npm install emotion-theming
- 使用
// 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
- Chakra UI 内置 Emotion,是 CSS-IN-JS 解决方案的集大成者
- 基于 Styled-Systems https://styled-system.com/
- 支持开箱即用的主题功能
- 默认支持白天和黑夜两种模式
- 拥有大量功能丰富且非常有用的组件
- 使响应式设计变得轻而易举
- 文档清晰而全面. 查找API更加容易
- 适用于构建用于展示的给用户的界面
- 框架正在变得越来越完善
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 颜色模式通用设置
- 设置默认颜色模式
通过 theme.config.initialColorMode 可以设置应用使用的默认主题.
- 使用操作系统所使用的颜色模式
通过 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>