# React

# 快速创建项目

npx create-react-app 项目名称
npx create-react-app 项目名称 --template typescript

# Class 组件

class Comp extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
    }
    this.inputRef = React.createRef()
  }

  handleChange = (e) => {}

  render() {
    return (
      <div>
        <input type="text" onChange={this.handleChange} ref={this.inputRef}/>
      </div>
    )
  }
}

class PureComp extends React.PureComponent {}

# 生命周期

componentWillMount() {} // 渲染前调用 
componentDidMount() {} // DOM Ready
componentWillReceiveProps(newProps) {} // Props 变化时调用
componentWillUpdate(prevProps, prevState) {} // Props 或 state 变化时调用
componentWillUnmount() {} // 组件从 DOM 中移除之前

# PropTypes (opens new window)

import PropTypes from 'prop-types'
Comp.PropTypes = {
  optionalArray: PropTypes.array,
  requiredArray: PropTypes.array.isRequired, // .isRequired 确保一定有。
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  optionalNode: PropTypes.node, // 任何可被渲染的元素(包括数字、字符串、元素或数组)
  optionalElement: PropTypes.element, // React 元素
  optionalElementType: PropTypes.elementType, // React 组件

  optionalEnum: PropTypes.oneOf(['News', 'Photos']),
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ])
}

# 默认属性值

Comp.defaultProps = {
  name: 'Joel',
  age: 18
}

# 函数组件

function Comp(props) {
  const [isOnline, setIsOnline] = useState(false)

  useEffect(() => {
    console.log('只执行一次')
  }, [])

  useEffect(() => {
    console.log('总是执行')
  })

  const handleChange = useCallback(e => {
  }, [])

  const inputRef = useRef(null)

  return (
    <div>
      <input type="text" onChange={handleChange} ref={inputRef}/>
    </div>
  )
}

const MemoComp = React.memo(Comp)

# useCallback 中用 useRef 获取最新值

const listRef = useRef(list)

const setListWithRef = (newList) => {
  setList(newList)
  listRef.current = newList
}

const renderList = useCallback(() => {
  return (<List list={listRef.current} setList={setListWithRef} />)
}, [])

# 动态列表子项的 DOM 的 Ref 数组

const itemList = useRef<HTMLDivElement[]>([]);
const setItemsRef = (dom: HTMLDivElement) => {
  itemRefList.current.push(dom);
};
return (
  <div>
    {list.map(item => {
      return (
        <div ref={setItemsRef} key={item.id}>...</div>
      )
    })}
  </div>
)

# 组件代码复用

# HOC - 高阶组件

// 定义
function withName (Component) {
  return (props) => <Component {...props} name="Joel"/>
}

// 使用。用装饰器看起来更舒服。
const WithNameComp = withName(Comp)

# RenderProps

// 定义
function Provider (props) {
  return <div>{props.children({name: 'Joel', ...props})}</div>
}

// 使用
<Provider>
  {(props) => {
    return <Comp name={props.name}/>
  }}
</Provider>

# 组合模式

render() {
  const newChildren = React.Children.map(this.props.children, (child, index) => {
    if (child.type) {
      return React.cloneElement(child, {
        active: this.state.activeIndex === index, // 子组件
        onClick: () => this.setState({ activeIndex: index }),
      })
    }
    return child
  })
}

# Refs 转发(forwardRef)

const Comp = React.forwardRef<HTMLDivElement>((props, ref) => {
  return (<div ref={ref}></div>);
}

const Par = () => {
  const compRef = useRef<HTMLDivElement>(null);
  const getCompWidth = () => compRef.current?.clientWidth || 0
  return (<Comp ref={compRef}/>)
}

# Context API

类组件

const ctx = React.createContext()
const { Provider, Consumer } = ctx

<Provider value={{name: 'joel'}}>
  <Consumer>
    {ctx => ( // Provider 上 value 属性的值
      <Component
        {...props}
        name={ctx.name}
      />
    )}
  </Consumer>
</Provider>

同时支持类组件和函数组件

const ctx = React.createContext()
const { Provider, Consumer } = ctx

// 父组件
export const XxProvider = ({chileren, ...value}) => {
  return (
    <Provider value={{name: 'joel', ...value}}>
      {children}
    </Provider>
  )
}

// 给类组件用
export function XxConnect(Wrapper: any) {
  return function WrappedComponent(props: any) {
        return (
            <Consumer>{value => <Wrapper {...props} {...value} />}</Consumer>
        );
    };
}

// 函数组件使用
function FnChild() {
  const nameCtx = useContext(ctx)
  return <div>{nameCtx.name}</div>
}

// 类组件使用
class ClassChild extends Component<IProps, IState> {

}
XxConnect(FnChild)

# Portals

ReactDOM.createPortal(
  jsxEl, // 一般是 this.props.children
  targetEl // Portal 到哪个元素下。document.body
)

# 事件

# 停止冒泡到原生的事件

<div onClick={e => e.nativeEvent.stopImmediatePropagation()}>...</div>

# 以捕获的方式绑定事件

事件名Capture。如

<div onClickCapture={...} />

# debounce 函数包裹事件函数的报错

报错:Uncaught TypeError: Cannot read property 'value' of null
原因:debounce包装后的回调函数,是个异步事件,即e.target为null了
解决方案:使用e.persist()实现对事件的引用保留

import { debounce } from 'lodash'

const didHandle = debounce(value => ...)

const handleKeywordChange = useCallback(event => {
  event.persist();
  didHandle(event.target.value)
})

# 问题

# Can’t perform a react state update on an unmounted component

原因:异步代码回调里改变状态,如果组件已经被卸载,会报这个错。
解决方案:设置状态前,判断组件是否已被卸载。

const [isUnMount, setIsUnMount] = useState(false)

useEffect(async () => {
  const data = await fetchSth()
  isUnMount && setData(data)
  return () => {
    setIsUnMount(true)
  }
}, [])

ahooks (opens new window) 的 API

import { useUnmountedRef } from 'ahooks'

const unmountRef: { current: boolean } = useUnmountedRef()
unmountRef.current && setData(data)

# 提前返回,导致 UseCallback 报错

if(!info) return;
const doSth = useCallback(...)
return ...

改成

const doSth = useCallback(...)
if(!info) return;
return ...

# 项目脚手架: CPA

文档 (opens new window)

npx create-react-app 项目名
# 创建 ts 项目
npx create-react-app 项目名 --template typescript

# 文档

— 完 —

整理By Joel (opens new window)。微信号搜索: joel007。