useState
const [state, setState] = useState(initialState)
说明:可在函数组件中管理状态
参数:
state
:状态值setState
:更改state状态的方法initialState
:状态的初始值
在第一次渲染组件的时候,会将initialState
的值赋给state
,当需要更改状态值时可以使用setState(xxx)
传入要更改的值
例:
function App() {
const [count, setCount] = useState(0)
return (
<div>
<h1>You clicked {count} times</h1>
<button onClick={() => setCount(count + 1)}> +1 </button>
<button onClick={() => setCount(count - 1)}> -1 </button>
)
}
函数式更新
当新的state
需要通过使用先前的state
计算得出的话,就可以传递一个函数
给setState。这个函数能接收先前的state
,并返回一个更新后的值
。
function App() {
const [count, setCount] = useState(0)
return (
<div>
<h1>You clicked {count} times</h1>
<button onClick={() => setCount(preState => preState + 1)}>函数式写法</button> //此处的preState的值就更新前的值
</div>
)
}
注意
由于与class组件中的setState
方法不同,useState
不会自动合并更新对象。需要配合使用展开运算符来达到合并更新对象的效果。const [state, setState] = useState({}); setState(prevState => { // 也可以使用 Object.assign return {...prevState, ...updatedValues}; });
惰性初始 state
由于在组件初始化的时候,会将initialState
作为初始值赋给state,并且该值也只在组件初始化中起作用,在后续渲染中会被忽略。
倘若初始state
需要通过复杂的计算得到的话,可以在将initialState
替换成一个函数
,然后在该函数中计算并返回值,同样的这个函数也只有在组件初次渲染中起作用。
例:
function App() {
const [count, setCount] = useState(() => {
const initialState = (10 - 9) * 5 / 3
return initialState
})
return (
<div>
<h1>You clicked {count} times</h1>
</div>
)
}
react是怎么保证多个useState的相互独立的?
当定义并且调用了多个useState
,传的参数也都是一个值时,也没有告诉react这些值对应的key,那么react是怎么保证多个useState都能找到其对应的state呢?
答案是:react按照定义useState
的顺序来确定的
例:
//第一次渲染
useState(42); //将age初始化为42
useState('banana'); //将fruit初始化为banana
useState([{ text: 'Learn Hooks' }]); //...
//第二次渲染,会根据顺序来替换数据
useState(42); //读取状态变量age的值(这时候传的参数42直接被忽略)
useState('banana'); //读取状态变量fruit的值(这时候传的参数banana直接被忽略)
useState([{ text: 'Learn Hooks' }]); //...
假如更改代码:
let showFruit = true;
function ExampleWithManyStates() {
const [age, setAge] = useState(42);
if(showFruit) {
const [fruit, setFruit] = useState('banana');
showFruit = false;
}
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
这样一来:
//第一次渲染
useState(42); //将age初始化为42
useState('banana'); //将fruit初始化为banana
useState([{ text: 'Learn Hooks' }]); //...
//第二次渲染
useState(42); //读取状态变量age的值(这时候传的参数42直接被忽略)
// useState('banana'); 这个useState就会读取不到
useState([{ text: 'Learn Hooks' }]); //此时这里的useState读取到的却是状态变量fruit的值,导致报错
因此,react规定我们必须要把hooks写在函数的最外层,不能写在一些条件语句中,以保证hooks的执行顺序是一致的。
useEffect
useEffect(function,[])
说明:可以在函数组件中进行副作用操作
参数:
-
function
:在组件第一次渲染或者后续的更新渲染中要进行的副作用;该函数可以有返回值,若有返回值,则返回的也需是个函数,并且该函数会被用来清除副作用(在组件被销毁时执行) -
[ ](可选)
:若没有
该参数,则会在每一次组件更新
时,执行副作用函数;若参数为空数组([])
,则只会在组件第一次
渲染时执行副作用函数;若参数的数组中有值
,则只有当该值发生变化
的时候会执行副作用函数。
useEffect(() => {
document.title = `You clicked ${count} times` //副作用函数
return () => {
console.log('cleanup') //清除副作用函数
}
}) //在每一次组件重新渲染时都会调用
//-------------------------------------------------
useEffect(() => {
document.title = `You clicked ${count} times` //副作用函数
return () => {
console.log('cleanup') //清除副作用函数
}
}, []) //在第一次渲染执行副作用函数
//-------------------------------------------------
useEffect(() => {
document.title = `You clicked ${count} times` //副作用函数
return () => {
console.log('cleanup') //清除副作用函数
}
}, [count]) //只有当count发生改变时才会执行副作用函数
useContext
const context = useContext(MyContext)
说明:可以帮助我们跨越组件层级直接传递变量,实现数据共享。
参数:
MyContext
:用React.createContext()
创建的上下文对象
它会返回由MyContext
传递过来的上下文值。
例:
import React, { useState, createContext, useContext } from 'react';
import ReactDOM from 'react-dom';
const CountContext = createContext(0); // 创建一个上下文,默认值为0
const App = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>父组件点击数量:{count}</p>
<button onClick={() => setCount(count + 1)}>{"点击+1"}</button>
<CountContext.Provider value={count}> {/* 使用CountContext.Provider包裹需要接收参数的子组件,并通过value将count传给子组件 */}
<Counter />
</CountContext.Provider>
</div>
);
};
const Counter = () => {
const count = useContext(CountContext); // 使用useContext获取上下文中的值,这里的值就是父组件传递的count
return (
<h1>子组件获得的点击数量:{count}</h1>
);
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
useReducer()
const [state,dispatch] = useReducer(reducer, initalArg, init)
说明:它是useState
的替代方案。能接收一个(state,action)=> newState
的reducer
,并且返回当前的state
以及其对应配套的dispatch
方法。
参数:
reducer
:reducer函数,(state,action)=> newState
initialArg
:参数初始值init
:函数,用来对初始值进行复杂计算
例:
const App = () => {
const [count, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
throw new Error();
}
}, 0);
return (
<div>
<h1>You clicked {count} times</h1>
<button onClick={() => { dispatch({ type: 'increment' }) }}>+ </button>
<button onClick={() => { dispatch({ type: 'decrement' }) }}>- </button>
</div>
);
};
当点击+按钮时,会根据type值去匹配
useReducer
中的第一个参数reducer函数里面的action.type
,然后返回其对应的值;
state
的值就是第二个参数给定的初始值
再有例:
const App = () => {
const initialValue = {
count: 0,
name: 'How',
};
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1, name: `increment${state.count}` };
case 'decrement':
return { count: state.count - 1, name: `decrement${state.count}` };
default:
throw new Error();
}
}, initialValue);
return (
<div>
<h1>You clicked {state.count} times</h1>
<h1>You name:{state.name}</h1>
<button onClick={() => { dispatch({ type: 'increment' }) }}>+ </button>
<button onClick={() => { dispatch({ type: 'decrement' }) }}>- </button>
</div>
);
};
可以定义个对象然后当做初始值传给
useReducer
当做第二个参数,同样的该对象也会赋值给第一个参数函数中的state。
惰性初始化
useReducer也可以像useState一样来惰性初始化state。当将第三个参数init函数传入时,这样初始state就被设置为init(initialArg)
const App = () => {
const [count, setCount] = useState(100);
return (
<AppF count={count} />
)
}
const AppF = ({ count }) => {
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1, name: `increment${state.count}` };
case 'decrement':
return { count: state.count - 1, name: `decrement${state.count}` };
default:
throw new Error();
}
}, count, (count) => {
return { count: count + 99, name: `initial${count}` };
});
return (
<div>
<h1>You clicked {state.count} times</h1>
<h1>You name:{state.name}</h1>
<button onClick={() => { dispatch({ type: 'increment' }) }}>+ </button>
<button onClick={() => { dispatch({ type: 'decrement' }) }}>- </button>
</div>
);
};
将外部传来值进行处理然后再将处理好的数据赋值给state
传来的count是0,进行了处理后值变为199
useMemo()
uesMemo(()=>{},[])
说明:一种性能优化的手段,可以减少不必要的渲染
参数:
函数
:该函数需要有一个返回值,并且返回值就是useMemo的返回值[ ]
:依赖项,当里面的依赖项发生变化了,就会引发组件的重渲染并且重新执行上面的函数。
例:
import React, { useState, useMemo, memo } from 'react';
import ReactDOM from 'react-dom';
let Con = memo((props) => {
console.log("render Con")
return (<div>{props.value}</div>)
})
function App() {
const [count, setCount] = useState(0);
const [value, setValue] = useState(0);
const cachedValue = useMemo(function () {
return count + 1
}, [count]) // 只有当count变化时才会执行
return (
<>
<div>{count}</div>
<div> {value}</div>
<Con value={cachedValue} />
<button onClick={() => setCount(v => v + 1)}>Add Count</button>
<button onClick={() => setValue(v => v + 1)}>Add Value</button>
</>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
useCallback()
useCallback(()=>{},[])
说明:useCallback就是useMemo的语法糖
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
参数:
函数
:回调函数,需要执行的代码语句[ ]
:依赖项,当里面的依赖项发生变化了,就会引发组件的重渲染并且重新执行上面的函数。
例:
function App() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1)
}, [count]);
return (
<>
<div>{count}</div>
<button onClick={increment}>Add Count</button>
</>
);
}
useRef()
const refContainer = useRef(initialValue)
说明:能获得相对应的DOM节点
参数:
initialValue
:默认值
它会返回一个可变的ref
对象,并且这个对象再组件的整个生命周期中是持续存在的,该对象只有一个current
属性,其初始值就是传入的initialValue
。
function App() {
const inputRef = useRef();
const getInput = () => {
console.log(ref);
console.log(ref.current);
}
return (
<>
<button onClick={getInput}>获取input</button>
<input ref={inputRef} style={{ border: '1px solid red' }} />
</>
);
}
例:
当我们需要点击按钮时获得这个输入框的焦点
function App() {
const inputRef = useRef();
const getInput = () => {
inputRef.current.focus();
}
return (
<>
<button onClick={getInput}>获取input</button>
<input ref={inputRef} style={{ border: '1px solid red' }} />
</>
);
}
forwardRef()和useImperativeHandle()
forwardRef
:可以接收父组件传递过来的ref
React.forwardRef((props, ref) => {})
//创建一个React组件,
//这个组件将会接受到父级传递的ref属性,
//可以将父组件创建的ref挂到子组件的某个dom元素上,
//然后在父组件就可以通过该ref就能获取到该dom元素
例:
const App = () => {
const sonInput = useRef(null);
return (
<>
<h1>父组件</h1>
<br />
<h1>子组件</h1>
<Son ref={sonInput} />
</>
)
}
const Son = forwardRef((props, ref) => {
return (
<>
<input ref={ref} /> //将父组件传递过来的ref赋值给input
<button onClick={() => {
console.log(ref.current) //点击按钮就可以获得当前input节点
}
}>点击</button>
</>
)
})
但是使用forwardRef
+useRef
来获取函数组件DOM时,获取的DOM的属性暴露了太多,此时就可以使用useImperativeHandle
来解决。
useImperativeHandle()
可以用来实现在函数组件的父组件中调用子组件的状态或者方法,可以自定义暴露给父组件
useImperativeHandle(ref, createHandle, [deps])
参数:
ref
: 由父组件传来的ref值createHandle
:是一个函数,会返回一个对象,这个对象中的属性会全部挂载在第一个参数ref
的current
属性上。[deps]
:依赖项,与useEffect,useCallback一样,当依赖项发生变化时,第二个参数就会重新执行,并且还会重新将属性挂载到第一个参数上面去。
例:
const App = () => {
const sonInput = useRef(null);
return (
<>
<Son ref={sonInput} /> {/* 将创建的ref传递给子组件*/}
<button onClick={() => sonInput.current.submit()}>submit</button> {/* 调用子组件中的submit方法 */}
<button onClick={() => sonInput.current.randomValue()}>生成一个随机数</button> {/* 调用子组件中的randomValue方法 */}
</>
)
}
const Son = forwardRef((props, ref) => {
const [value, setValue] = useState('');
const randomValue = useCallback(() => {
setValue(Math.round(Math.random() * 100) + '');
}, [])
const submit = useCallback(() => {
if (value) {
alert(`提交成功${value}`);
} else {
alert('请输入内容');
}
}, [value])
useImperativeHandle(ref, () => { //获取父组件传来的ref,并且暴露randomValue,submit这两个方法给父组件
return {
randomValue,
submit
}
}, [randomValue, submit])
return (
<div className="box">
<h2>子组件</h2>
<section>
<label>用户名:</label>
<input
value={value}
placeholder="请输入用户名"
onChange={e => setValue(e.target.value)}
/>
</section>
<br />
</div>
)
})
useLayoutEffect()
useLayoutEffect(() => { doSomething })
与 useEffect
Hooks 类似,都是执行副作用操作。但是它是在所有 DOM 更新完成后触发。可以用来执行一些与布局相关的副作用,比如获取 DOM 元素宽高,窗口滚动距离等等。
注意:进行副作用操作时尽量优先选择 useEffect,以免阻止视觉更新。
其用法与useEffect
相似,但是会在useEffect
之前执行
例:
const App = () => {
const divRef = useRef(null);
const [height, setHeight] = useState(100);
useLayoutEffect(() => {
// DOM 更新完成后打印出 div 的高度
console.log('useLayoutEffect: ', divRef.current.clientHeight);
})
return <>
<div ref={divRef} style={{ background: 'red', height: height }}>Hello</div>
<button onClick={() => setHeight(height + 50)}>改变 div 高度</button>
</>
}