React Hooks
Hooks
1. React Hooks 介绍
1.1 React Hooks 是用来做什么的
对函数型组件进行增强,让函数型组件可以存储状态,可以拥有处理副作用的能力,让开发者在不使用类组件的情况下,实现相同的功能。
1.2 类组件的不足(Hooks 要解决的问题)
- 缺少逻辑复用的机制
为了复用逻辑增加无实际渲染效果的组件,增加了组件层级,显示十分臃肿,增加了调试的难度以及运行效率的降低
- 类组件经常会变得很复杂难以维护
将一组相干的业务逻辑拆分到了多个生命周期函数中,在一个生命周期函数内,存在多个不相干的业务逻辑
- 类成员方法不能保证 this 指向的正确性
2. React Hooks 使用
Hooks 意为钩子, React Hooks 就是一堆钩子函数, React 通过这些钩子函数对函数型组件进行增强,不同的钩子函数提供了不同的功能
2.1 useState()
用于为函数组件引入状态
import {useState} from 'react'
function App() {
const [count, setCount] = useState(0)
return (
<div>
<span>{count}</span>
<button onClick={()=>setCount(count+1)}> + 1</button>
</div>
);
}
export default App;
- 接受唯一的参数即状态初始值,初始值可以是任意数据类型。
const [count, setCount] = useState(0)
const [person, setPerson] = useState({ name: '张三', age: 20 })
- 返回值为数组,数组中存储值和更改状态值的方法,方法名称约定以 set 开头,后面加上状态名称。
const [count, setCount] = useState(0)
- 方法可以被调用多次,用以保存不同状态值
<button onClick={()=>setCount(count+1)}> + 1</button>
<button onClick={()=>setPerson({name: '李四', age: 30})}>setPerson</button>
<button onClick={()=>setPerson({...person, name: '李四'})}>setPerson(只改变一个属性,其他属性不变)</button>
- 参数可以是一个函数,函数返回什么,初始状态值就是什么,函数只会被调用一次,在初始值是动态值的情况。
// 当初始值是动态值
// 这样写每次渲染都会执行
const propsCount = props.count || 0
// const [count, setCount] = useState(propsCount)
// 应该这样写
const [count, setCount] = useState(() => {
return props.count || 0 // 只有第一次执行的时候才会执行
})
- 设置状态值方法的参数可以是一个值也可以是一个函数,设置状态值方法的方法本身是异步的
如果代码依赖状态值,那要写在回调函数中:
function handleCount () {
setCount((count) => {
const newCount = count + 1
document.title = newCount
return newCount
})
}
<button onClick={handleCount}> + 1</button>
2.2 useReducer()
useReducer 是另一种让函数组件保存状态的方式,可以将 dispatch 传给子组件使用
import { useReducer } from "react";
export default 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>
<button onClick={() => dispatch({type: 'decrement'})}>-1</button>
<span>{count}</span>
<button onClick={() => dispatch({type: 'increment'})}>+1</button>
</div>
)
}
2.3 useContext()
在跨组件层级获取数据时简化获取数据的代码
import { createContext, useContext } from "react";
const countContext = createContext()
export default function App () {
return (
<countContext.Provider value={1000}>
<Foo />
</countContext.Provider>
)
}
function Foo () {
const value = useContext(countContext)
return (
<div>I am Foo {value}</div>
)
}
2.4 useEffect()
让函数型组件拥有处理副作用的能力,类似生命周期函数
- useEffect 执行机制
可以把 useEffect 看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合
useEffect(() => {}) => componentDidMount, componentDidUpdate
useEffect(() => {}, []) => componentDidMount
useEffect(() => () => {}) => componentDidUpdate, componentWillUnmount
useEffect(() => () => {}, []) => componentWillUnmount
import { useEffect, useState } from "react";
import ReactDOM from 'react-dom'
export default function App () {
const [count, setCount] = useState(0)
// 组件挂载完成之后执行,组件数据更新之后执行
// useEffect(() => {
// console.log('123')
// })
// 组件挂载完成之后执行
// useEffect(() => {
// console.log('456')
// }, [])
useEffect(() => {
return () => {
console.log('组件被卸载了')
}
})
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count+1)}>+1</button>
<button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>卸载组件</button>
</div>
)
}
- useEffect 使用方法
(1). 为 window 对象添加滚动事件
(2). 设置定时器让 count 数值每隔一秒增加 1
import { useEffect, useState } from "react";
import ReactDOM from 'react-dom'
export default function App () {
const [count, setCount] = useState(0)
function onScroll () {
console.log('页面滚动了')
}
useEffect(() => {
window.addEventListener('scroll', onScroll)
return () => {
window.removeEventListener('scroll', onScroll)
}
}, [])
useEffect(() => {
const timerId = setInterval(() => {
setCount(count => {
const newCount = count + 1
document.title = newCount
return newCount
})
}, 1000);
return () => {
clearTimeout(timerId)
}
}, [])
return (
<div>
<span>{count}</span>
<button onClick={() => { ReactDOM.unmountComponentAtNode(document.getElementById('root')) }} >卸载组件</button>
</div>
)
}
-
useEffect 解决的问题
(1). 按照用途将代码进行分类(将一组相同的业务逻辑归置到了同一个副作用函数中)
(2). 简化重复代码,是组件内部代码更加清晰 -
只有指定数据发生变化时触发 effect
import { useEffect, useState } from "react";
export default function App () {
const [count, setCount] = useState(0)
const [person, setPerson] = useState({name: '张三'})
useEffect(() => {
// person 的变化不会触发 useEffect , 因为第二个数组参数中只监听了 count
document.title = count
console.log(111)
}, [count])
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<button onClick={() => setPerson({name: '李四'})}>setPerson</button>
</div>
)
}
- useEffect 钩子函数结合异步函数
useEffect 中的参数函数不能是异步函数,因为 useEffect 函数要返回清理资源的函数,如果是异步函数就变成了返回 Promise. 可以写成自执行函数的形式:
useEffect(() => {
(async () => {
await axios.get()
})()
}, [])
2.5 useMemo()
useMemo 的行为类似 Vue 中的计算属性,可以检测某个值的变化,根据变化只计算新值。
useMemo 会缓存计算结果,如果检测子没有发生变化,及时组建重新渲染,也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算。
import { useState, useMemo } from "react";
export default function App () {
const [count, setCount] = useState(0)
const [bool, setBool] = useState(true)
const result = useMemo(() => {
console.log('111') // 只有 count 改变才会重新执行这个回调函数
return count * 2
}, [count])
return (
<div>
<span>{count} {result}</span>
<span>{bool ? '真' : '假'}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<button onClick={() => setBool(!bool)}>setBool</button>
</div>
)
}
2.6 memo 方法
性能优化,如果本组件中的数据没有发生变化,阻止组件更新,类似类组件中的 PureComponent 和 shouldComponentUpdate
import { memo } from 'react'
const Foo = memo(function Foo () {
return <div>I am Foo</div>
})
2.7 useCallback()
性能优化,缓存函数,使用组件重新渲染时得到相同的函数实例。否则每次父组件渲染,函数变量的实例都会变化,导致里层组件被重新渲染
import { useState, memo, useCallback } from "react";
const Foo = memo(function Foo (props) {
console.log('Foo 重新渲染了')
return <div>
<span>I am Foo</span>
<button onClick={props.resetCount}>resetCount</button>
</div>
})
export default function App () {
const [count, setCount] = useState(0)
const resetCount = useCallback(() => {
setCount(0)
}, [setCount])
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<Foo resetCount={resetCount} />
</div>
)
}
2.8 useRef()
2.8.1 获取DOM元素对象
import { useRef } from "react";
export default function App () {
const box = useRef()
return (
<div ref={box}>
<button onClick={() => console.log(box)}> DIV </button>
</div>
)
}
2.8.2 保存数据(跨组件周期)
即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染。
import { useRef, useState, useEffect} from "react";
export default function App () {
const [count, setCount] = useState(0)
let timeId = useRef() // 夸组件生命周期
useEffect(() => {
// 使用这个 ref 的 current 属性存储数据
timeId.current = setInterval(() => {
setCount(count => count + 1)
}, 1000);
}, [])
const stopCount = () => {
console.log(timeId.current)
clearInterval(timeId.current)
}
const box = useRef()
return (
<div ref={box}>
<span>{count}</span>
<button onClick={() => stopCount()}> 停止 </button>
</div>
)
}
3. 自定义 Hook (为了在组件之间形成逻辑共享)
自定义 Hook 是标准的封装和共享逻辑的方式
自定义 Hook 是一个函数,其名称以 use 开头
自定义 Hook 其实就是逻辑和内置 Hook 的组合
如何使用自定义 Hook:
import { useState, useEffect} from "react";
import axios from "axios";
function useGetPost () {
const [post, setPost] = useState({})
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => {
setPost(response.data)
})
}, [])
return [post, setPost]
}
export default function App () {
const [post, setPost] = useGetPost()
return (
<div>
<h1>{post.title}</h1>
<div>{post.body}</div>
</div>
)
}
封装公共逻辑:
import { useState} from "react";
function useUpdateInput (initialState) {
const [value, setValue] = useState(initialState)
return {
value,
onChange: e => setValue(e.target.value)
}
}
export default function App () {
const usernameInput = useUpdateInput('')
const passwordInput = useUpdateInput('')
const submitForm = event => {
event.preventDefault();
console.log(usernameInput.value)
console.log(passwordInput.value)
}
return (
<form onSubmit={submitForm}>
<input type="text" name="username" {...usernameInput} />
<input type="password" name="password" {...passwordInput} />
<input type="submit" />
</form>
)
}
4. React 路由 Hooks
4.1 react-router-dom 路由提供的钩子函数
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from "react-router-dom";
import App from './App';
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root')
);
App.js
import { Link, Route } from "react-router-dom";
import Home from './pages/Home'
import List from './pages/List'
export default function App () {
return (
<>
<div>
<Link to="/home/zhangsan">首页</Link>
<Link to="/list">列表页</Link>
</div>
<div>
<Route path="/home/:name" component={Home} />
<Route path="/list" component={List} />
</div>
</>
)
}
Home.js
import { useHistory, useLocation, useRouteMatch, useParams } from "react-router-dom";
export default function Home(props) {
console.log(props)
console.log(useHistory())
console.log(useLocation())
console.log(useRouteMatch())
console.log(useParams())
return <div>
<h1>Home works</h1>
</div>;
}
输出结果:
{history: {…}, location: {…}, match: {…}, staticContext: undefined}
{length: 7, action: “PUSH”, location: {…}, createHref: ƒ, push: ƒ, …}
{pathname: “/home/zhangsan”, search: “”, hash: “”, state: undefined, key: “o6w5y3”}
{path: “/home/:name”, url: “/home/zhangsan”, isExact: true, params: {…}}
{name: “zhangsan”}
List.js
export default function List(props) {
console.log(props)
return <div>
<h1>List works</h1>
</div>;
}
5. useState 钩子函数的实现原理
使用数组 state 存储状态,用数组 setters 存储 setState方法,利用闭包管理,将 下标 stateIndex 缓存在闭包中,创建 对应的 setState.
// import { useState } 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
ReactDOM.render(
<App/>,
document.getElementById('root')
)
}
export default function App () {
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')
return <div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<span>{name}</span>
<button onClick={() => setName('李四')}> 李四</button>
</div>
}
6. useEffect 钩子函数的实现原理
// import { useState } 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')
)
}
// 上一次的依赖值
let prevDepsAry = []
let effectIndex = 0
/**
* useEffect
* @param {function} callback 回调函数
* @param {Array} depsAry 依赖数组
* @returns {function} 清理函数
*/
function useEffect (callback, depsAry) {
if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect 第一个参数必须是一个函数')
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]) : true
// 判断值是否有变化
if (hasChanged) {
callback()
}
// 同步依赖值
prevDepsAry[effectIndex++] = depsAry
}
}
export default function App () {
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')
useEffect(() => {
console.log('Hello')
}, [count])
useEffect(() => {
console.log('World')
}, [name])
// 测试不传监听数据的情况
useEffect(() => {
console.log('xxx')
}, [])
return <div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<span>{name}</span>
<button onClick={() => setName('李四')}> 李四</button>
</div>
}
7. useReducer 钩子函数的实现原理
// import { useState } from 'react'
// import { useReducer } 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')
)
}
// 上一次的依赖值
let prevDepsAry = []
let effectIndex = 0
/**
* useEffect
* @param {function} callback 回调函数
* @param {Array} depsAry 依赖数组
* @returns {function} 清理函数
*/
function useEffect (callback, depsAry) {
if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect 第一个参数必须是一个函数')
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]) : true
// 判断值是否有变化
if (hasChanged) {
callback()
}
// 同步依赖值
prevDepsAry[effectIndex++] = depsAry
}
}
function useReducer (reducer, initialState) {
const [state, setState] = useState(initialState)
function dispatch (action) {
const newState = reducer(state, action)
setState(newState)
}
return [state, dispatch]
}
export default function App () {
function reducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
default:
return state
}
}
const [cnt, dispatch] = useReducer(reducer, 0)
return <div>
<div>
<button onClick={() => dispatch({type: 'increment'})}> + 1</button>
<span>{cnt}</span>
<button onClick={() => dispatch({type: 'decrement'})}> - 1</button>
</div>
</div>
}
Formik
React 表单增强
1. Formik
1.1 Formik 介绍
增强表单处理能力,简化表单处理流程
官网
1.2 Formik 下载
npm install formik
2. Formik 增强表单
2.1 Formik 基本使用
使用 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 表单验证
2.2.1 初始验证方式
const formik = useFormik({
validate: values => {
const errors = {}
if (!values.username) errors.username = '请输入用户名'
return errors
}
})
return <form>{formik.errors.username ? <div>{formik.errors.username}</div> : null}</form>
2.2.2 表单验证(二)
增加 onBlur 事件,显示时先判断 formik.touched.username 是否存在
<input
type="text"
name="username"
value={formik.values.username}
onChange={formik.handleChange}
onBlur={formik.handlerBlur}
/>
<p>{formik.touched.username && formik.errors.username ? formik.errors.username : null}</p>
2.2.3 使用 yup 验证
- 下载 yup
npm install yup
- 引入 yup
import * as Yup from 'yup'
- 定义验证规则
validationSchema: Yup.object({
username: Yup.string()
.max(15, '用户名的长度不能大于15')
.required('请输入用户名'),
password: Yup.string()
.max(20, '密码的长度不能大于20')
.required('请输入密码')
})
2.3 减少样板代码
{...formik.getFieldProps('username')}
2.4 使用组件的方式构建表单
import { Formik, Form, Field, ErrorMessage } from 'formik'
import * as Yup from 'yup'
export default function App () {
const initialValues = {
username: '张三',
content: '我是内容',
subject: 'java',
}
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" />
<button type="submit">提交</button>
</Form>
</Formik>
)
}
2.5 构建其他表单项
默认情况下,Field 组件渲染的是文本框,如要生成其他表单元素可以使用以下语法
<Field as="textarea" name="content" />
<Field as="select" name="subject" >
<option value="java">java</option>
<option value="js">js</option>
</Field>
2.6 使用useField 构建自定义表单控件
function MyInputField ({ label, ...props }) {
const [field, meta] = useField(props)
return <div>
<label htmlFor={props.id}>{label}</label>
<input {...field} {...props} />
<span>{ meta.touched && meta.error ? meta.error: null }</span>
</div>
}
2.7 自定义复选框组件
function Checkbox ({ label, ...props }) {
const [field, meta, helper] = useField(props)
const {value} = meta
const { setValue } = helper
const handleChange = () => {
const set = new Set(value)
if (set.has(props.value)) {
set.delete(props.value)
} else {
set.add(props.value)
}
setValue([...set])
}
return <div>
<label htmlFor="" >
<input checked={value.includes(props.value)} type="checkbox" {...props} onChange={handleChange} />{label}
</label>
</div>
}
<Checkbox value="足球" label="足球" name="hobbies" />
<Checkbox value="篮球" label="篮球" name="hobbies" />
<Checkbox value="橄榄球" label="橄榄球" name="hobbies" />
const initialValues = {
hobbies: ['足球', '篮球']
}
CSS-IN-JS
集成 CSS 代码在 JS 文件
1. 为什么会有 CSS-IN-JS
CSS-IN-JS 是 WEB 项目中将 CSS 代码捆绑在 JS 代码中的解决方案,这种方案旨在解决 CSS 的局限性,例如缺乏动态功能,作用域和可移植性。
2. CSS-IN-JS 介绍
CSS-IN-JS 方案的优点:
- 让CSS代码拥有独立的作用域,阻止CSS代码泄露到组件外部,防止样式冲突。
- 让组件更具可移植性,实现开箱即用,轻松创建松耦合的应用程序。
- 让组件更具可重用性,只需编写一次即可,可以在任何地方运行.不仅可以在同一-应用程序中重用组件,而且可以在使用相同框架构建的其他应用程序中重用组件。
- 让样式具有动态功能,可以将复杂的逻辑应用于样式规则,如果要创建需要动态功能的复杂UI,它是理想的解决方案。
3. Emotion 库
3.1 Emotion 介绍
Emotion 是一个旨在使用 JS 编写 CSS 样式的库
npm install @emotion/core @emotion/styled
3.2 css 属性支持
- JSX Pragma
通知 babel , 不再需要将 jsx 语法转换为 React.createElement 方法, 而是需要转换为 jsx 方法。
/** @jsx jsx */
import { jsx } from '@emotion/core'
- Babel Preset
npm run eject
npm install @emotion/babel-preset-css-prop
在 package.json 文件中找到 babel 属性,加入如下内容:
"presets": [
"react-app",
"@emotion/babel-preset-css-prop"
]
Chakra-UI
现代化 React UI 框架 Chakra-UI
1. Chakra-UI 介绍
Chakra UI是一个简单的,模块化的易于理解的UI组件库.提供了丰富的构建React应用所需的U|组件.
文档: https://next.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/react @emotion/react @emotion/styled framer-motion
2.2 克隆默认主题
Chakra-UI 提供的组件是建立在主题基础之上的,只有先引入了主题组件才能够使用其他组件。
npm install @chakra-ui/theme
2.3 引入主题
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import theme from '@chakra-ui/theme'
import { ChakraProvider } from "@chakra-ui/react"
ReactDOM.render(
<ChakraProvider theme={theme}>
<App />
</ChakraProvider>,
document.getElementById('root')
);
2.4 依赖清单
{
"name": "chakra-ui-guide",
"version": "0.1.0",
"private": true,
"dependencies": {
"@chakra-ui/react": "^1.0.4",
"@chakra-ui/theme": "^1.2.2",
"@emotion/react": "^11.1.3",
"@emotion/styled": "^11.0.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"framer-motion": "^3.1.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
2.5 引用组件
import { Button } from '@chakra-ui/react'
function App() {
return (
<div>
<Button>submit</Button>
</div>
);
}
export default App;
3. Style Props 样式属性
Style Props 是用来更改组件样式的,通过为组件传递属性的方式实现。通过传递简化的样式属性一达到提升开发效率的目的。可以在https://chakra-ui.com/docs/features/style-props查看样式简写
Prop | CSS Property | Theme Key |
---|---|---|
m , margin | margin | space |
mt , marginTop | margin-top | space |
mr , marginRight | margin-right | space |
mb , marginBottom | margin-bottom | space |
ml , marginLeft | margin-left | space |
mx | margin-left and margin-right | space |
my | margin-top and margin-bottom | space |
p , padding | padding | space |
pt , paddingTop | padding-top | space |
pr , paddingRight | padding-right | space |
pb , paddingBottom | padding-bottom | space |
pl , paddingLeft | padding-left | space |
px | padding-left and padding-right | space |
py | padding-top and padding-bottom | space |
如何使用:
import { Box } from '@chakra-ui/react'
function App() {
return (
<div>
<Box w={256} h={200} bg='tomato'>Box</Box>
</div>
);
}
export default App;
4. 主题
4.1 颜色模式(color mode)
chakra-ui 提供的组件都支持两种颜色模式,浅色模式(light)和暗色模式(dark).
可以通过 useColorMode 进行颜色模式的更改。
import { Box, Text, Button, useColorMode } from '@chakra-ui/react'
function App() {
const {colorMode, toggleColorMode} = useColorMode() // 注意这里是对象解构,不是数组解构
return (
<Box w={256} h={200} bg='tomato'>
<Text>当前的颜色模式为 {colorMode}</Text>
<Button onClick={toggleColorMode}>切换颜色模式</Button>
</Box>
);
}
export default App;
Chakra 将颜色模式存储在 localStorage 中,并使用类名策略来确保颜色模式是持久的
4.2 根据颜色模式设置样式
chakra 允许在为元素设置样式时根据颜色模式产生不同值。通过 useColorModeValue 钩子函数实现
import { Box, useColorModeValue } from '@chakra-ui/react'
const bg = useColorModeValue('tomato', 'skyblue')
<Box w={256} h={200} bg={bg}></Box>
4.3 强制组件颜色模式
使组件不受颜色模式的影响,始终保持在某个颜色模式下的样式,使用 LightMode 组件包裹需要作用的组件只显示浅色模式,,使用 DarkMode 组件包裹需要作用的组件只显示暗色模式
import { Button, LightMode } from '@chakra-ui/react'
<LightMode>
<Button onClick={toggleColorMode}>切换颜色模式</Button>
</LightMode>
4.4颜色模式通用设置
-
设置默认颜色模式
通过 theme.config.initialColorMode 可以设置应用使用的默认主题. -
使用操作系统所使用的颜色模式
通过 theme.config.useSystemColorMode 可以设置将应用的颜色模式设置为操作系统所使用的颜色模式.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import theme from '@chakra-ui/theme'
import { ChakraProvider } from "@chakra-ui/react"
// 1. 设置默认颜色模式
// theme.config.initialColorMode = 'dark'
// 2. 使用操作系统所使用的颜色模式
theme.config.useSystemColorMode = true
ReactDOM.render(
<ChakraProvider theme={theme}>
<App />
</ChakraProvider>,
document.getElementById('root')
);
4.5 主题对象
- Colors
在设置颜色时,可以但不限于取主题对象中的颜色值
function App() {
return <Box w={200} h={200} bg="cyan.500"></Box>
}
- Space
使用 space 可以自定义项目间距。这些间距值可以由 width, height 和 maxWidth, minWidth 等样式引用
<Box mt={6} w="lg" h="2xl" ></Box> <!-- lg => 32rem -->
- Breakpoints
配置响应数组值中使用的默认断点。这些值将用于生成移动优先(即最小宽度)的媒体查询
// theme.js
export default {
breakpoints: ["30em", "48em", "62em", "8Øem"] ,
};
<Box fontSize={["12px", "14px", "16px", "18px", "20px"]}></Box>
4.6 创建标准的Chakra-Ul组件
- 创建 Chakra-Ul 组件
import {chakra} from '@chakra-ui/react'
const LaGouButton = chakra("button", {
baseStyle: {
borderRadius: 'lg'
},
sizes: {
sm: {
px: '3', // padding-left/padding-right
py: '1', // padding-right/padding-bottom
fontSize: '12px'
},
md: {
px: '4',
py: '2',
fontSize: '14px'
}
},
variants: { // 风格化样式
primary: {
bgColor: 'blue.500',
color: 'white'
},
danger: {
bgColor: 'red.500',
color: 'white'
}
}
});
LaGouButton.defaultProps = {
size: 'sm',
variant: 'primary'
};
function App() {
return (
<div>
<LaGouButton> 按钮 </LaGouButton>
</div>
)
}
export default App;
- 全局化Chakra-UI组件样式
a. 在 src 文件夹中创建 LaGou 文件夹用于放置自定义Chakra-UI组件
b. 在 lagou 文件夹中创建 Button.js 文件并将组件样式放置于当前文件中并进行默认导出
const LaGouButton = {
baseStyle: {
borderRadius: 'lg'
},
sizes: {
sm: {
px: '3', // padding-left/padding-right
py: '1', // padding-right/padding-bottom
fontSize: '12px'
},
md: {
px: '4',
py: '2',
fontSize: '14px'
}
},
variants: { // 风格化样式
primary: {
bgColor: 'blue.500',
color: 'white'
},
danger: {
bgColor: 'red.500',
color: 'white'
}
},
defaultProps: {
size: 'sm',
variant: 'primary'
}
}
export default LaGouButton
c. 在 LaGou 文件夹中创建 index.js 文件用于导入导出所有的自定义组件
import LaGouButton from './Button'
export default {
LaGouButton
}
d. 在 src 文件夹中的 index.js 文件中导入自定义 Chakra-UI 组件并和 components 属性进行合并
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import theme from '@chakra-ui/theme'
import { ChakraProvider } from "@chakra-ui/react"
import LaGouComponents from './LaGou'
const myTheme = {
...theme,
components: {
...theme.components,
...LaGouComponents
}
}
console.log(myTheme)
ReactDOM.render(
<ChakraProvider theme={myTheme}>
<App />
</ChakraProvider>,
document.getElementById('root')
);
e. 在组件中使用样式化组件
import {chakra} from '@chakra-ui/react'
const LaGouButton = chakra("button", {
themeKey: 'LaGouButton'
});
function App() {
return (
<div>
<LaGouButton> 按钮 </LaGouButton>
</div>
)
}
export default App;