对函数型组件进行增强, 让函数型组件可以存储状态, 可以拥有处理副作用的能力.
让开发者在不使用类组件的情况下, 实现相同的功能.
Hooks 意为钩子, React Hooks 就是一堆钩子函数, React 通过这些钩子函数对函数型组件进行增强, 不同的钩子函数提供了不同的功能.
用于为函数组件引入状态
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>;
}
使用说明:
设置状态值方法的参数可以是一个值也可以是一个函数.
设置状态值方法的方法本身是异步的.
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>
}
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>
}
在跨组件层级获取数据时简化获取数据的代码.
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>;
}
让函数型组件拥有处理副作用的能力. 类似生命周期函数.
为window对象添加滚动事件,组件卸载时移除
设置定时器让count数值每隔一秒增加1 ,组件卸载时清除定时器
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>
}
useEffect(() => {
document.title = count
}, [count])
useEffect中的参数函数不能是异步函数, 因为useEffect函数要返回清理资源的函数, 如果是异步函数就变成了返回Promise
useEffect(() => {
(async () => {
await axios.get()
})()
})
useMemo 的行为类似Vue中的计算属性, 可以监测某个值的变化, 根据变化值计算新值.
useMemo 会缓存计算结果. 如果监测值没有发生变化, 即使组件重新渲染, 也不会重新计算. 此行为可以有助于避免在每个渲染上进行昂贵的计算.
import {
useMemo } from "react";
const result = useMemo(() => {
return count *2
}, [count])
性能优化, 如果本组件中的数据没有发生变化, 阻止组件更新. 类似类组件中的 PureComponent 和 shouldComponentUpdate
会返回一个新的组件
import {
memo } from "react";
const Counter = memo(function Count(){
return <div></div>
})
export default Counter;
性能优化, 缓存函数, 使组件重新渲染时得到相同的函数实例. 防止组件因为函数实例不同而重新渲染
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>
})
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>
}
主要目的就为了实现,组件之间的数据共享
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>
}
react-router-dom 路由提供的钩子函数
import {
useHistory,
useLocation,
useRourteMatch,
useParams
} from 'react-router-dom'
// 获取对应的路由对象
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>;
}
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++;
}
}
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>;
}
增强表单处理能力. 简化表单处理流程.
https://jaredpalmer.com/formik/
官网:
https://github.com/jquense/yup#readme
npm install 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>
}
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 判断表单元素是否被改动过
下载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 => {
}
});
减少样板代码
{
...formik.getFieldProps('username')}
可以代替
onchange={
formik.handleChange}
onBlur={
formik.handleBlur}
value={
formik.values.username}
组价
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>
);
}
默认情况下, 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>
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 代码在 JavaScript 文件
CSS-IN-JS 是 WEB 项目中将 CSS 代码捆绑在 JavaScript 代码中的解决方案.
这种方案旨在解决 CSS 的局限性, 例如缺乏动态功能, 作用域和可移植性.
将css与js放在一起,方便组件移植,并且让其有自己的作用域,与VUE 中的 style 中的 scoped 作用一样。
优点:
缺点:
推荐使用CSS-IN-JS方案
Emotion 是一个旨在使用 JavaScript 编写 CSS 样式的库. 实现 CSS-IN-JS 方案
npm install @emotion/core @emotion/styled
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
"presets":[
"react-app",
"@emotion/babel-preset-css-prop"
]
之后便可在组件中直接使用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>
props 对象中的 css 属性优先级高于组件内部的 css 属性.
在调用组件时可以在覆盖组件默认样式.
及外部优先级大于内部的
样式化组件就是用来构建用户界面的,是 emotion 库提供的另一种为元素添加样式的方式。
用法
import {
styled } from '@emotion/styled';
const Conter = styled.div` //也可使用对象形式
width: 100px;
height: 100px;
`;
function App() {
return <div>
App works
<Conter/>
</div>;
}
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>;
}
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>;
}
如果不生效 (需要 babel 插件支持)
babel 插件配置
"babel": {
"plugins": ["emotion"]
}
& 表示组件本身.
要使用组件中的样式, 但要更改呈现的元素, 可以使用 as 属性.
as 可以改变组件的标签;
在样式组合中, 后调用的样式优先级高于先调用的样式. 后覆盖前的;
引入 Global 组件
引入 keyframes 组件
import {
css, keyframes } from '@emotion/core';
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;
现代化 React UI 框架 Chakra-UI
Chakra UI 是一个简单的, 模块化的易于理解的 UI 组件库. 提供了丰富的构建 React 应用所需的UI组件.
文档:
https://next.chakra-ui.com/docs/getting-started
https://chakra-ui.com/docs/getting-started
npm install @chakra-ui/[email protected]
Chakra-UI 提供的组件是建立在主题基础之上的, 只有先引入了主题组件才能够使用其他组件.
npm install @chakra-ui/theme
引入
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")
);
chakra-ui 提供的组件都支持两种颜色模式, 浅色模式(light)和暗色模式(dark). 可以通过 useColorMode 进行颜色模式的更改.
Chakra 将颜色模式存储在 localStorage 中, 并使用类名策略来确保颜色模式是持久的.
chakra 允许在为元素设置样式时根据颜色模式产生不同值. 通过 useColorModeValue 钩子函数实现.
使组件不受颜色模式的影响, 始终保持在某个颜色模式下的样式.
通过 theme.config.initialColorMode 可以设置应用使用的默认主题.
通过 theme.config.useSystemColorMode 可以设置将应用的颜色模式设置为操作系统所使用的颜色模式.
在设置颜色时, 可以但不限于取主题中提供的颜色值.
<Box color="red.500">Box>
使用 space 可以自定义项目间距. 这些间距值可以由 padding, margin 和 top, left, right, bottom 样式引用.
<Box mb="5">Box>
使用 size 可以自定义元素大小, 这些值可以由 width, height 和 maxWidth, minWidth 等样式引用.
<Box w="lg">Box>
配置响应数组值中使用的默认断点. 这些值将用于生成移动优先(即最小宽度)的媒体查询.
// 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>
官网:
http://react-icons.github.io/react-icons/