React初学总结

79 阅读7分钟

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.propsthis.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) 此生命周期在后代组件抛出错误后被调用。 它接收两个参数:
  1. error —— 抛出的错误。

  2. info —— 带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息 componentDidCatch() 会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况

其他APIs

  • setState()
  • forceUpdate() 强制让组件重新渲染 跳过shouldComponentUpdate()
  • defaultProps defaultProps 可以为 Class 组件添加默认 props。这一般用于 props 未赋值,但又不能为 null 的情况

生命周期图

常用生命周期图

image.png

全部生命周期图

image.2.png

#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 相当于 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合 相当于是在函数组件中的生命周期,并且里面的数据是相当于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在组件挂载和卸载时执行,可以把第二个参数传入一个[]来实现