jsx可以让我们直接在js代码里书写html(前提要引入React),例如
const JSXDemo = () => { const data = {key: 'value'} return ( <> <div>this is jsx</div> <div>key is {data.key}</div> </> ) }类似javascript,可以有if else、三元表达式、短路表达式等 条件渲染
const ConditionDemo = () => { const theme = 'black' const blackBtn = <button className={style.blackBtn}>black btn</button> const whiteBtn = <button className={style.whiteBtn}>white btn</button> // if else if(theme === 'black') { return whiteBtn } else { return blackBtn } // 在jsx中使用三元表达式 return ( <> { theme === 'black' ? blackBtn : whiteBtn } </> ) // jsx中使用短路表达式 return ( <> {theme === 'black' && blackBtn} </> ) }在jsx中渲染list,直接将含有jsx的数组进行渲染即可,例如
const ListDemo = () => { const originList = [ { id: 'id-1', title: '标题1' }, { id: 'id-2', title: '标题2' }, { id: 'id-3', title: '标题3' } ]; const [list] = useState(originList) return ( <> <ul> {/* arr.map产出一个jsx的数组即可在页面上渲染 */} {list.map((item, index) => <li key={item.id}>index: {index}; title: {item.title}</li>)} </ul> </> ) }react的事件绑定中有this指向问题,不过这是针对class component来说的,默认的this是undefined。其中的event对象不是原生,而且在react中事件的target和currentTarget不同,后者是document,即react中所有的事件都挂载在document上。
export default class EventDemo extends React.Component { constructor(props) { super(props) // 这种效率最高,因为bind只执行一次 this.handleClick2.bind(this) } handleClick1(arg) { console.log(arg) } handleClick2(e, arg) { console.log(e.target, arg) } handleClick3 = (e, args) => { console.log(e.target, args); // <butto> event 2</button> #document console.log(e.nativeEvent.target, e.nativeEvent.currentTarget); } render() { return ( <> {/* 每次点击都会返回一个新的函数 */} <button onClick={this.handleClick1.bind(this, 'args')}>event 1</button> <button onClick={e => this.handleClick2(e, 'args')}> event 2</button> <button onClick={e => this.handleClick3(e, 'args')}>event 3</button> </> ) } }综上得出:
event是合成事件,模拟出DOM事件的全部功能event.nativeEvent是原生事件对象所有的事件都挂载到了document上和DOM事件不一样,和Vue事件也不一样父子组件一般通过props进行通信(层级不深的时候)
import React, { useState } from 'react' import PropTypes from 'prop-types' const Input = ({ addListItem, length }) => { const [value, setValue] = useState('') const onInputChange = e => setValue(e.target.value) const submit = () => { if (value) { addListItem({ id: length + 1, title: value }) setValue('') } } const onKeyUp = e => { if (e.which === 13) { submit() } } return ( <> <input value={value} onChange={onInputChange} onKeyUp={onKeyUp} /> <button onClick={submit}>提交</button> </> ) } const List = ({ list, removeItem }) => { return <ul>{list.map((item, index) => <li onClick={() => removeItem(item.id)} key={item.id}><span>{index}.{item.title}</span></li>)}</ul> } List.propTypes = { list: PropTypes.array.isRequired, removeItem: PropTypes.func.isRequired } export default () => { const [list, setList] = useState([ { id: 1, title: '标题1' } ]) const addListItem = item => { const newList = [...list, item] setList(newList) } const removeItem = id => { const newList = list.filter(item => item.id !== id) setList(newList) } return ( <> <Input addListItem={addListItem} length={list.length} /> <List list={list} removeItem={removeItem} /> </> ) }关于useState中的set方法和class component中的setState,均为不可变值,只能通过调用setState等赋值新的值或地址,可能是异步更新,也可能会被合并(一次性多次set) 举个例子
export default class StateDemo extends React.Component { constructor(props) { super(props) this.state = { count: 0 } } increase = () => { // setCount(count + 1) // console.log(count); // 这里拿不到最新的值 // 多次设置,直接传入对象,合并为一次 // const count = this.state.count // this.setState({ count: count + 1 }) // this.setState({ count: count + 1 }) // this.setState({ count: count + 1 }) // 定时器中setState是同步的 setTimeout(() => { this.setState({ count: this.state.count + 1 }) console.log('count in setTimeout', this.state.count) }, 0); // 自己定义的DOM事件,setState是同步的 // 见componentDidMound // 多次设置,传入函数时,不合并,同步执行 // const count = this.state.count // this.setState((prevState, props) => ({ count: prevState.count + 1 })) // this.setState((prevState, props) => ({ count: prevState.count + 1 })) // this.setState((prevState, props) => ({ count: prevState.count + 1 })) // 同步 // Promise.resolve().then(() => { // this.setState({ // count: this.state.count + 1 // }) // console.log(this.state.count); // }) } componentDidMount(){ // 自己定义的DOM事件,setState是同步的 document.body.addEventListener('click', () => { this.setState({count: this.state.count + 1}) console.log(this.state.count) }) } render() { return ( <div> <p>{this.state.count}</p> <button onClick={this.increase}>累加</button> </div> ) } }总的来说,setState是异步还是同步基于设置时的方式: setState时位于基于event loop形式的回调函数里,则为同步,反之为异步(注意:如果setState传入函数时,是同步)
单组件生命周期 父子组件生命周期 和vue类似:
创建阶段:父 -> 子渲染阶段:componentWillMount 父 -> 子, componentDidMount 子 -> 父更新阶段:父组件更新数据,子组件更新数据;componentDidUpdate 子 -> 父卸载阶段:componentDidUnMount 子 -> 父函数组件不需要去理解class的生命周期,是一个纯函数,输入一个props,输出jsx。函数组件
import React, { Fragment, useState, useCallback, useEffect, useContext } from 'react' // redux中还有useSelector useDispatch // react-router还有useLocation、useHistory等hooks export default () => { return ( <Fragment> </Fragment> ) }react函数组件可以使用hook,来管理数据和上下文等。这些后面介绍
非受控组件使用场景:
必须手动操作dom元素,setState实现不了文件上传 <input type="file">某些富文本编辑器,需要传入dom元素优先使用受控组件,必须操作DOM时,使用非受控组件
有的时候,需要将子组件渲染到父组件外部(例如模态框、弹出层等),这个时候就需要使用Portals
import React, { Fragment } from 'react' import ReactDOM from 'react-dom' import style from './index.module.css' export default ({ children }) => { // 将子组件渲染到body,避免弹出层嵌套层级过深 return ReactDOM.createPortal(<div className={style.modal}>{children}</div>, document.body) // return ( // <Fragment> // <div className={style.modal}> // {children} // </div> // </Fragment> // ) }context一般用于跨组件通讯,如果用redux的话小题大做。context有关的api有:useContext、createContext,一般和useReducer配合来完成类似redux的数据管理
import React, { Fragment, createContext, useContext, useReducer } from 'react' const defualtTheme = 'light' const THEME = 'THEME' const themeCtx = createContext(defualtTheme) function ThemeReducer(state, action) { switch (action.type) { case THEME: // 因为reducer是纯函数,所以每次数据的改动都必须返回一个新的对象 return { ...state, theme: action.payload } default: return state } } const Link = () => { // 子组件获取数据 const { value } = useContext(themeCtx) return ( <div>{value.theme}</div> ) } export default () => { const [reducer, dispatch] = useReducer(ThemeReducer, { theme: defualtTheme }) const changeTheme = () => { dispatch({ type: THEME, payload: 'dark' }) } return ( <Fragment> {/* 传递数据到子组件 */} <themeCtx.Provider value={{ dispatch, value: reducer }}> <Link /> <button onClick={changeTheme}>切换主题</button> </themeCtx.Provider> </Fragment> ) }和Vue一样,React中也是通过import()进行动态加载组件的,不过这个不常用,最常用的是React.lazy配个React.Suspense配合使用
import React, { Fragment } from 'react' // import() 方式 // const load = () => import('./ContextDemo') // export default () => { // const loadComponent = () => { // load() // } // return ( // <Fragment> // <button onClick={loadComponent}>点击加载组件</button> // </Fragment> // ) // } // 异步加载组件React.lazy和React.suspense // function showImport(fn, delay) { // return new Promise(resolve => { // setTimeout(() => resolve(fn), delay) // }) // } const Component = React.lazy(() => import('./ContextDemo.js')) export default () => { return ( <React.Suspense fallback="加载中...."> <Component /> </React.Suspense> ) }React中父组件更新,子组件无条件也更新;SCU不建议使用深度比较
shouldComponentUpdate for class component, useEffect for functional component(类组件使用SCU进行优化,函数组件使用useEffect)PureComponent(浅层比较,可使用immutable.js进行控制)、React.memo(hook)使用immutable.js问题:
为什么SCU默认返回true,而要提供PureComponent供开发人员使用,为什么不默认就是PureComponent的形式? 因为SCU只有在需要的时候需要,将优化的权力交给开发;高阶组件并非是一种功能,而是一种模式。相当于高阶函数(或装饰器)。在redux中,connect就是一个高阶组件
import React, { Component } from 'react' // 高阶组件, const withComponent = (Comp) => { return class withComponent extends Component { constructor(props) { super(props) this.state = { x: 0, y: 0 } } handleMouseMove = e => { this.setState({ x: e.clientX, y: e.clientY }) } render() { return ( <div onMouseMove={this.handleMouseMove} style={{ height: 500, width: 500, background: '#ccc', color: '#fff' }}> <Comp mousePosition={this.state} {...this.props} /> </div> ) } } } class App extends Component { constructor(props) { super(props); this.state = {} } render() { return (<div>x:{this.props.mousePosition.x}, x:{this.props.mousePosition.y}, </div>); } } export default withComponent(App)Render Props通过一个函数将class的state作为props传递给纯函数组件,与高阶组件相反,它是将内部的state传递到外部进行渲染。
import React, { Component } from 'react' import PropTypes from 'prop-types' class Factory extends Component { constructor(props) { super(props); this.state = { x: 0, y: 0 } } handleMouseMove = e => { this.setState({ x: e.clientX, y: e.clientY }) } render() { return (<div onMouseMove={this.handleMouseMove}>{this.props.render(this.state)}</div>); } } Factory.prototypes = { render: PropTypes.func.isRequired } const App = () => { return ( <Factory render={props => <div style={{ height: 500, width: 500, background: '#ccc', color: '#fff' }}>x: {props.x}, y:{props.y}</div>}></Factory> ) } export default AppHOC vs Render Props:
HOC: 模式简单,但是会增加组件嵌套层级,代码比较难看懂Render Props:代码简洁,学习成本较高Redux是一个javascript的数据或状态state管理框架,并不但用于react,但是却完美结合了react关于不可变值和纯函数的理念。
store、action、reducer
概述:dispatch(action) -> reducer -> newState -> subscribe触发订阅 -> 更新视图
react-redux:Provider、connect、mapStateToProps、mapDispatchToProps、useSelector、useDispatch、useStore
使用redux-thunk
