react的两种组件
- 函数式组件
function Hello (props) {
return (
<h1>Hello, {props.name}</h1>
)
}
- Class组件
class Welcome extends React.Component {
constructor (props) {
super(props)
this.state = {}
}
render () {
// 每次setState都会执行render
reutrn (
<div>
Welcome, {this.props.name}
</div>
)
}
}
事件处理
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
onClick={handleClick}
或
onClick = {function () { handleClick() }}
或
onClick = {() => handleClick()}
如果传入的是handleClick()则会在页面出来时执行一次,之后再次点击不会执行
onClick = {handleClick()}
注意点
改变state里面的值只能通过构造函数来修改
setState({ x: xx })
State 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因此 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
this.setState((state, props) => ({
x: xx
}))
// 或
this.setState((state, props) => {
return {
x: xx
}
})
// 或
this.setState(function (state, props) {
return {
x: xx
}
})
props是单向数据流,一直向下流动的,props是只读的,props可以传递函数,方法,值。
给dom绑定方法
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
class Hello extends React.Component {
constructor (props) {
super(props)
this.state = {
value: null
}
// 解决this指向问题 第一种办法
this.handleClick = this.handleClick.bind(this)
}
// 解决this指向第三种办法
// handleClick = () => {
//
// }
handleClick () {
this.setState((state) => ({
value: ++state.value
}))
}
render () {
// 解决this指向 第二种办法
// let btn = <button onClick={() => this.handleClick()}>value值加1</button>
let btn = <button onClick={this.handleClick}>value值加1</button>
return (
<div>
value的值:{this.state.value}
{btn}
</div>
)
}
}
组件之间的事件传递
class Btn extends React.Component {
constructor (props) {
super(props)
}
handleClick = () => {
this.props.onClickBtn(Math.random())
}
render () {
return (
<button
onClick={this.handleClick}
>
{this.props.name}
</button>
)
}
}
class Value extends React.Component {
constructor (props) {
super(props)
this.state = {
name: 'value值累加',
value: 1
}
}
handleClick (num) {
let { value } = this.state
this.setState({
value: value + num
})
}
render () {
let { name, value } = this.state
return (
<div>
value的值:{value}
<Btn
name={name}
onClickBtn={(num) => this.handleClick(num)}
/>
</div>
)
}
}
组件的条件渲染和循环
- 组件的条件渲染需要在render外面就通过if判断好,或者在JSX里面通过三元表达式或与或非的方式来渲染
function Test (props) {
let btn = null
if (props.show) {
btn = <button>show</button>
} else {
btn = <button>not show</button>
}
return (
<div>
{btn}
{props.show ? 'show' : 'not show'}
{props.show && 'show'}
</div>
)
}
- 组件的循环直接通过遍历得到的数据构建成一个dom列表
function List () {
let nums = [1,2,3,4,5]
const listItems = nums.map(item =>
<li key={item}>{item}</li>
)
return (
<ul>{listItems}</ul>
)
}
// 在JSX里面map
function List1 () {
let nums = [1,2,3,4,5]
return (
<ul>
{
nums.map(item =>
<li key={item}>{item + 1}</li>
)
}
</ul>
)
}
注意点
- 不能使用props.key来获取key
- 在哪里map就在哪里设置key
- 一个好的经验法则是:在
map()方法中的元素需要设置 key 属性。 - key最好不要为索引值,会影响diff算法
组件中的插槽(props的传递)
- 默认的插槽 props.children
function Fancy (props) {
return (
<div>
{props.children}
</div>
)
}
function Parent () {
return (
<Fancy>
<h1>我是Fancy的props.children</h1>
</Fancy>
)
}
- 自定义插槽
function Fancy (props) {
return (
<div>
<div>
{props.left}
</div>
<div>
{props.right}
</div>
</div>
)
}
function Parent (props) {
return (
<Fancy
left={ <h1>我是左边的</h1> }
right={ <h2>我是右边的</h2> }
/>
)
}
多个组件需要反映相同的变化数据,将共享状态提升到最近的共同父组件中去一起操作。
组件生命周期
class组件继承了React.Component这个基类,有很多的生命周期方法
挂载阶段
当组件实例被创建并插入 DOM 中时
- contructor() class的构造函数里面对props和state进行一个初始化,为事件处理函数绑定实例
- static getDerivedStateFromProps(prevProps, prevState) 不常用 调用时机在:1.render方法之前调用 2.初始挂载 3 后续更新时调用 可以在里面拿到更新前的props值和state值,一般在state的值任何时候都取决于props时使用
- render() 必须定义的函数,用来渲染ReactDom元素
- componentDidMount 组件挂载完成后执行 类似vue里面的(created mounted),一般在这里面进行实例化操作
更新阶段
当组件的props或state发生变化时会触发更新
- static getDerivedStateFromProps
- shouldComponentUpdate() 根据返回值来判断当前的React组件是否受当前state或props更改的影响。如果return false,将不会执行render()
- render()
- getSnapshotBeforeUpdate
在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给
componentDidUpdate() - componentDidUpdate(prevProps,prevState, snapshot) 在更新后会立即调用,不能再这里面直接调用setState(),会导致死循环,要通过一些条件来调 可以拿到更新前的props,state以及通过getSnapshotBeforeUpdate() 返回的参数
卸载阶段
当组件从DOM中移除时会调用
- componentWillUnmount() 组件卸载及销毁之前调用,执行一些必要的清理操作,相当于vue的destroyed,不应该调用setState(),因为组件已经卸载了不会再重新渲染了
错误处理
渲染过程中,生命周期,或子组件的构造函数中抛出错误时调用
- static getDerivedStateFromError 此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state,在渲染阶段调用,不允许出现其他情况
- componentDidCatch(error, info) 此生命周期在后代组件抛出错误后被调用。 它接收两个参数:
-
error—— 抛出的错误。 -
info—— 带有componentStackkey 的对象,其中包含有关组件引发错误的栈信息componentDidCatch()会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况
其他APIs
- setState()
- forceUpdate() 强制让组件重新渲染 跳过shouldComponentUpdate()
- defaultProps
defaultProps可以为 Class 组件添加默认 props。这一般用于 props 未赋值,但又不能为null的情况
生命周期图
常用生命周期图
全部生命周期图
#Hook浅试 如果要在函数组件里面改变数据来更新页面,就需要用到Hook
useState()
useState()相当于就是在函数组件里面实现的一个setState()
在后续的重新渲染中,
useState返回的第一个值将始终是更新后最新的 state。
- 函数式更新 如果新的state需要通过之前的state计算得出,可以传递一个函数给setState,参数就是先前的state,返回的就是更新后的值。
function Item () {
const [count, setCount] = useState(0) // 初始值为0
function handleClick () {
setCount(count => count + 1)
}
return (
<div>
{count}
<button onClick={handleClick}>自增1</button>
</div>
)
}
- 惰性初始 state
initialState参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
const [state, setState] = useState(() => {
const initalState = getInit(props)
return initalState
})
useRef()
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数( initialValue )。返回的 ref 对象在组件的整个生命周期内持续存在
换句话说,每次useState()修改页面后,会全部执行整个函数组件,如果是直接定义不会保存现有的值,而是初始值,使用useRef()创建的变量会保存现有的值,不会根据刷新来初始化值。
function Item () {
const [count, setCount] = useState(0) // 初始值为0
const ref = {
current: 1
}
const ref1 = useRef(1)
function handleClick () {
setCount(count => count + 1)
ref.current += 1
ref1.current += 1
console.log(ref.current, ref1.current) // (2,2) (2,3) (2,4)
}
return (
<div>
{count}
<button onClick={handleClick}>自增1</button>
</div>
)
}
useEffect()
useEffect Hook 相当于 componentDidMount , componentDidUpdate 和 componentWillUnmount 这三个函数的组合
相当于是在函数组件中的生命周期,并且里面的数据是相当于useRef(),会保存现有的值,他使用了闭包的机制
使用场景:第一次渲染和每次react组件渲染后在useEffect里面执行相应操作
无需清除的 effect
function Item () {
const [count, setCount] = useState(0) // 初始值为0
useEffect(() => {
// 第一次渲染以及组件重新更新后会执行
// 值不会初始化,会保持现有状态
document.title = `你点击了${count}次`
})
function handleClick () {
setCount(count => count + 1)
}
return (
<div>
{count}
<button onClick={handleClick}>自增1</button>
</div>
)
}
需要清除的 effect
例如在componentDidMount订阅了一个通知,就需要在componentWillUnmount去取消订阅这个通知,这个时候可以通过返回一个取消订阅的函数来实现清除的 effect
当effect返回了一个函数时会在组件卸载的时候去执行清除操作
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
effect可以在同一函数组件使用多个,他们之前不会相互影响,实现关注点分离
effect的第二个参数
如果你想跳过这次effect的执行,可以传递第二个参数,effect会在渲染之后比较第二个参数值是否和渲染之前的值一样,如果一样就会直接跳过这次effect,否则才会执行,提高了性能
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
// 如果count渲染之前为5,渲染之后还是为5则不会执行该effect,如果渲染之后不会5则会执行该effect
如果只想effect在组件挂载和卸载时执行,可以把第二个参数传入一个
[]来实现