0.课前提问
1.什么是ui(核心主旨)
2.状态是数据吗
3.什么是状态
0.1 什么是ui(核心主旨)
UI= f(data),准确吗?
函数f将数据(data)映射到用户界面(ui)
例如:[good1,good2,good3] =》goodList
0.2 状态是数据吗?不是
0.3 状态(state)是什么?
状态有一个隐含的意思,就是存在改变状态的行为(behavior) 。
例如:点赞数(likes-状态)隐含了增加一个赞(addLike-行为);而这个
行为(点赞)只有在某中上下文(context)中才会存在。
(ps:对上下文的理解可以是我们共同知道的一些东西,比如说我们都是中国人,能够听得懂中文,生活在中国很多年是我们的上下文)
我们需要,重新再思考UI是什么
1.hooks的基本概念
1.1 什么是React Hooks
- React 16.8的新增特性,可以让你在不写class的情况下使用state
等react特性。 - hooks是对函数式组件的极大加强
1.2 如何描述UI
前言
看上去UI= f(data),但是实际上还有很多的内置函数定时器,页面跳转这些
1.旧的思考方式
UI从数据开始,数据产生视图,用户点击视图产生消息,消息触发重算机制来重算数据,产生循环
像vue,react,angular的模型都是这样的单向数据流模型
-
1.消息->重算 抽象成行为
消息和重算是视图和数据背后的行为
-
2.行为
行为中也有异步行为
-
3.数据拆分:不变的是属性,变化的是状态
以不变的属性传进视图,再用行为驱动状态的变化
-
4.状态映射了行为,因此行为可以封装在状态内
比如说我想点赞,点赞是一个行为,行为引申出来是用户看到的视图,视图背后是他的状态。简化一下就是ui是状态和视图的循环。状态分为同步行为和异步行为也就是同步状态异步状态。
(异步状态最典型的就是promise。promise是用来描述一个即将发生的值也可以是未来的值。他背后代表的值,不是一个函数,不是用来调用的,就是一个值)
状态也可以代表未来的一个状态,所以状态可以含有行为
比如说:流是什么?字符流,文件流,数据流。流代表一个未来的值。代表未来可能发生的所以值的集合。流看上去是一个状态或者是一个数据,但其实背后有一个时间的关系。
所以我们可以说状态是可以发生变化的。状态完全可以包含它的行为而不让视图去感知。视图只要感知状态而无需感知行为。
-
5.让作用可以监听所有的一切
发现在原来的模型上,光有状态和视图,还不够。因为还差作用,比如说用户console.log应该怎么算?不算在视图和状态里(UI描述),那应该怎么算?
比如跳转功能算什么?不算状态,因为没有和数据和行为对上关系。什么也不改变就直接跳走了。所以抽象出另一个东西叫作用,而window.location.href 就算是一种作用
作用:状态和视图之外的。作用需要上下文。这个上下文就是作用需要理解有哪些状态。
比如说根据状态做出的跳转。但这不是状态背后的行为。他是个作用,因为用户没有登录是一个布尔值,他并不是去改变这个布尔值,而是直接跳转了,从而产生了UI的变化。
-
6.这些概念如何和视图关联?
我们看到的就是一个视图,而状态,作用,上下文这些都可以看做一种行为
举个例子,用户想要跳转到一个URL,那么这个跳转URL本质上是一个作用,但这个作用也是一种行为。
状态,比如说点赞,点赞增加的是一个数字,这个数字是可以和视图绑定上的,但是本身点赞也是一种行为
所以无论是状态,作用还是之后的一些概念,这些行为都是和视图关联上的。而这种关联我们要尽量做到松散的耦合,便于复用。因为如果都写在视图里就难以进行复用了。
这种关联的关系,我们称作hooks。
1.3 什么是Hooks
重新定义UI界面是什么
主要包括这些要素:state hook,effect hook,context hook函数V =f(props,state) V视图
UI = V usehook1(),usehook2()...
一个视图使用了第一个hook,使用了第二个hook
比如说点赞的视图,点赞的hook提供了点赞的能力和点赞的状态
2.三个基础的hook
2.1 状态
在某个上下文中(用户界面)数据和改变数据的行为
const [count,setCount] = useState(0)
count是状态
setCount是行为
useState是hooks API
React hooks帮助我们将数据和行为绑定
import React,{ useState} from 'react';
function useCount(){
//useCount就是一个描述,描述状态背后的行为
const [count, setCount] = useState(0)
return [count,()=>{
setCount(count+1)
}]
}
export default function Example(){
// const [count, setCount] = useState(0);
const [count, addCount] = useCount(0);
//定义状态和关联的行为,他俩是一组
return (
Your count:{count}
{/* */}
)
}
click触发setCount行为,行为触发状态的变化,最终导致UI的变化.也可以自定义hook
改变状态的值需要依赖他的行为,数据和行为是强关联,而数据和UI是弱关联,这样就达成了和UI的解耦
状态一定要和他的行为封装在一起,这样才能形成一个独立的行为模块
2.2 作用 Effect
作用(Effect)
- 1.UI如果这样实现,它不仅仅是一个将数据映射到视图的函数。
- 2.客观世界存在输入和输出之外的东西(改变URL,改变环境....)
UI=data->{
console.log( 'xxxx' )
return< ... / div>
}
虽然Example是函数调用,但是要把它理解成一个描述,描述的时候需要一个作用(useInterval)。每次render,作用就执行一次
useEffect理解为依赖xx变化的作用
//读作:依赖count变化的作用
useEffect( log. bind(null,count), [count]);
//读作:依赖[变化]变化的作用
useEffect( log.bind(null,count)) ;
只要function渲染有任何的状态变化,那就需要执行一次effect
依赖空只变化一次,因为空不会变化
2.2.1 React Hooks如何描述作用(Effect)
1.客观世界有url、计时器、logger.....我们做不到完美而纯净(pure functional)的视图渲染。
2.相同(或类似)的作用如何进行复用,React团队提出了这个方案。
useEffect ( someEffect )
function someEffect() {
console.log (...)
}
封装定时器组件
import React,{ useState,useEffect } from 'react';
function useInterval(callback, time){
useEffect( ()=>{
const I= setInterval(callback, time);
return ()=>{
//在return里销毁定时器
clearInterval();
}
},[])
//useEffect只执行一次,因为依赖是空,空不会改变
}
export default function Example(){
const [count, setCount] = useState(0);
useInterval(()=>{
//setCount(count + 1)
setCount(count=>count + 1)
},1000)
return (
Your count:{count}
)
}
- useInterval使用效果(effect).当发生依赖的时候,函数
(()=》{} )才被注入到效果里。
- 使用效果的时候用的是最初版本的callback,count=0,count+1=1,count被函数缓存下来了
导致结果打印1 1 1 1 ..
count值没有改变,但计时器一直在执行
因为每次执行example都会创造一个新的函数(useInterval里面),每次使用最初版本count=0,count被函数缓存下来了
- 解决方法是设置成函数 setCount(count=>count + 1)
设置成函数,react会帮助你取到最新的count
2.3 上下文
上下文就是你理解事物需要的背景知识。
- UI产生的过程中,能够从context中获取信息(知识)
- UI更像一个人而不是机械的结构。
UI => (data) => {
const {userType} = useContext (UserTypeContext)
switch (userType) {
...
不同的渲染逻辑
}
}
改变背景色的案例
import React, { useContext, useState } from 'react';
const themes = {
light: {
foreground: "#000000",
background: '#eeeeee'
},
dark: {
foreground: "#ffffff",
background: '#222222'
}
}
//定义创建上下文
const ThemeContext = React.createContext({
theme: themes.light,
toggle: () => { }
})
export default () => {
// const [count, setCount] = useState(0);
const [theme, setTheme] = useState(themes.light);
//xxx.Provider,xxx作为上下文提供者
return (
{
setTheme(theme => {//获取最新的theme作为setTheme的参数
setTheme(theme === themes.light ? themes.dark : themes.light)
})
}
}}>
)
}
const Toolbar = () => {
return
}
const ThemedButton = () => {
const context = useContext(ThemeContext)
return (
)
}
联系生活,把一个人放在一个地方,他要做事情就得依赖他所处的环境
2.4 Reducer
(设计模式)提供一种抽象状态行为的通用封装(action),以及计算过程的抽象方案(reducer)
reducer其实是一个函数,把action映射成为状态.把很多action映射为一个状态.通过reducer实现一个状态背后有多个行为
import React, { useReducer } from "react"
function reducer(state, action) {
switch (action.type) {
case "add":
return state + 1;
case "sub":
return state - 1;
default:
throw "...";
}
}
export default function Counter() {
const [counter, dispatch] = useReducer(reducer,0)
return (
Your counter is : {counter}
)
}
2.6 引用行为 ref
引用React管理以外的对象
需要在React之外做一些事情:例如:focus、 媒体对象操作等
通常搭配useEffect
附带作用:方便地保存值
import React, { useRef,useState } from 'react';
export default function UseRefExample() {
const [counter, setCounter] = useState(0);
// let prev = null
const prev = useRef(null);
//null代表reference初始值
return (
当前值:{counter}
{/* 之前的值:{prev}
*/}
之前的值:{prev.current}
)
}
注释部分代码是有问题的,因为每次进入useRefExample,prev都被置为null,但是如果放到函数外面会造成污染。因为如果有其他函数使用prev会影响到当前函数里的prev
2.5.2 缓存
为什么要缓存? .
- V = f(state, props) useHooks...
- 想在f中new Object(); 只创建一次
- 一些复杂的计算只有在状态改变后才做
- 缓存一个函数(useCallback)
- 缓存一个值(useMemo)
3.使用hook是的建议
Tips1:使用React.memo减少重绘次数
Tips2: hooks同步问题
使用作用时要避免副作用,比如说把定时器回收
Tips3:可以构造自己的hooks封装行为
//需求:模拟获取接口数据,并显示在页面上
//1.写一个函数getPerson模拟调用接口函数,sleep函数模拟延迟效果
//2.写一个函数usePerson封装状态和作用(使用useState,useEffect)
//3.使用自定义hooks,根据是否有数据在页面上显示不同的效果
import React, { useState,useEffect } from 'react'
function sleep(time){
return new Promise((reslove)=>{
setTimeout(() => {
reslove()
}, time);
})
}
async function getPerson(){
await sleep(200)
return ['1','2','3']
}
function usePerson(){
const [list, setList] = useState(null)
async function request(){
const list = await getPerson()
return setList(list)
}
useEffect(request,[])
return list
}
export default () => {
const list = usePerson()
if(list===null){
return data is loading
}else{
return (
{
list.map((name,i)=>{
return - {name}
})
}
)
}
}
Tips4:每种行为一个Hook
Tips5:不要再思考生命周期