React Hooks 学习笔记

React Hooks

Hooks

1. React Hooks 介绍

1.1 React Hooks 是用来做什么的

对函数型组件进行增强,让函数型组件可以存储状态,可以拥有处理副作用的能力,让开发者在不使用类组件的情况下,实现相同的功能。

1.2 类组件的不足(Hooks 要解决的问题)
  1. 缺少逻辑复用的机制

为了复用逻辑增加无实际渲染效果的组件,增加了组件层级,显示十分臃肿,增加了调试的难度以及运行效率的降低

  1. 类组件经常会变得很复杂难以维护

将一组相干的业务逻辑拆分到了多个生命周期函数中,在一个生命周期函数内,存在多个不相干的业务逻辑

  1. 类成员方法不能保证 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;
  1. 接受唯一的参数即状态初始值,初始值可以是任意数据类型。
const [count, setCount] = useState(0)
const [person, setPerson] = useState({ name: '张三', age: 20 })
  1. 返回值为数组,数组中存储值和更改状态值的方法,方法名称约定以 set 开头,后面加上状态名称。
const [count, setCount] = useState(0)
  1. 方法可以被调用多次,用以保存不同状态值
<button onClick={()=>setCount(count+1)}> + 1</button>
<button onClick={()=>setPerson({name: '李四', age: 30})}>setPerson</button>
<button onClick={()=>setPerson({...person, name: '李四'})}>setPerson(只改变一个属性,其他属性不变)</button>
  1. 参数可以是一个函数,函数返回什么,初始状态值就是什么,函数只会被调用一次,在初始值是动态值的情况。
// 当初始值是动态值
// 这样写每次渲染都会执行
const propsCount = props.count || 0
// const [count, setCount] = useState(propsCount)

// 应该这样写
const [count, setCount] = useState(() => {
  return props.count || 0 // 只有第一次执行的时候才会执行
})
  1. 设置状态值方法的参数可以是一个值也可以是一个函数,设置状态值方法的方法本身是异步的
    如果代码依赖状态值,那要写在回调函数中:
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()

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

  1. 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>
  )
}
  1. 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>
  )
}
  1. useEffect 解决的问题
    (1). 按照用途将代码进行分类(将一组相同的业务逻辑归置到了同一个副作用函数中)
    (2). 简化重复代码,是组件内部代码更加清晰

  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>
  )
}
  1. 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 验证
  1. 下载 yup
npm install yup
  1. 引入 yup
import * as Yup from 'yup'
  1. 定义验证规则
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 方案的优点:

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

3. Emotion 库

3.1 Emotion 介绍

Emotion 是一个旨在使用 JS 编写 CSS 样式的库

npm install @emotion/core @emotion/styled
3.2 css 属性支持
  1. JSX Pragma

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

/** @jsx jsx */
import { jsx } from '@emotion/core'
  1. 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

  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/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查看样式简写

PropCSS PropertyTheme Key
m, marginmarginspace
mt, marginTopmargin-topspace
mr, marginRightmargin-rightspace
mb, marginBottommargin-bottomspace
ml, marginLeftmargin-leftspace
mxmargin-left and margin-rightspace
mymargin-top and margin-bottomspace
p, paddingpaddingspace
pt, paddingToppadding-topspace
pr, paddingRightpadding-rightspace
pb, paddingBottompadding-bottomspace
pl, paddingLeftpadding-leftspace
pxpadding-left and padding-rightspace
pypadding-top and padding-bottomspace

如何使用:

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颜色模式通用设置
  1. 设置默认颜色模式
    通过 theme.config.initialColorMode 可以设置应用使用的默认主题.

  2. 使用操作系统所使用的颜色模式
    通过 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 主题对象
  1. Colors

在设置颜色时,可以但不限于取主题对象中的颜色值

function App() {
  return <Box w={200} h={200} bg="cyan.500"></Box>
}
  1. Space

使用 space 可以自定义项目间距。这些间距值可以由 width, height 和 maxWidth, minWidth 等样式引用

<Box mt={6} w="lg" h="2xl" ></Box> <!-- lg => 32rem -->
  1. Breakpoints

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

// theme.js
export default {
  breakpoints: ["30em", "48em", "62em", "8Øem"] ,
};
<Box fontSize={["12px", "14px", "16px", "18px", "20px"]}></Box>
4.6 创建标准的Chakra-Ul组件
  1. 创建 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;
  1. 全局化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;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值