组件间通信 — 父传子(通过props传递)
不使用hook
- 父组件通过子组件标签定义的属性,向子组件传递数据,子组件通过{this.props.属性}来获取,事件写法差不多
- 示例:
1
2
3
4
5
6
7
8
9
10
11// 在父组件中调用子组件
<Children name="父传子" />
//子组件
export const Children extends Component {
contructor(props) {
super(props)
}
render(
return(<div>{this.props.name}</div>)
)
}
💚使用hook
- 父组件在子组件标签上通过子组件定义的事件或属性上传递,子组件使用解构从props对象中拿到对应变量
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 父组件
const parent = memo(() => {
return (
<div>
<Child title="我是父组件传递给子组件的变量" />
</div>
)
})
// 子组件
interface IChildProps {
title: string
}
const Child = memo<IChildProps>((props) => {
const { title } = props
})
export default Child
组件间通信 — 子传父
- 子组件定义事件,通过参数传递,父组件在子组件标签中通过箭头函数拿到参数,完成传递
不使用hook
1 | // 父组件 |
💚使用hook
1 | // 父组件 |
给组件添加交互功能(箭头函数)
- 在组件的标签中定义一个事件 onclick= {() => {}} 的prop
组件记住被交互过(state)
不使用hook
- 在组件的构造函数上设置this.state来初始化state(构造函数必搭配super(props))
1
2
3
4
5
6constructor(props) {
super(props);
this.state = {
value: null,
};
} - 将组件中要记住的值用this.props.xxx替换为this.state.xxx
- 将对应的监听事件改为 onClick={() => this.setState({value: ‘X’})}
💚使用hook
- 示例:const [state, setState] = useState(null)
不可变性!!!
- 用新数据替换旧数据(仍保留原数据)
- 时间旅行(撤销和恢复)
- 跟踪数据的改变
- 创建pure components,可确定不可变数据是否改变,从而确定何时重新渲染
时间旅行
- 保存历史记录 —slice()保存副本到history
- 将state提升到顶层,把原本的state改为props
- (已经记录完)展示历史步骤记录 — map()
JSX
- 语法: <标签>文本,{变量/函数}</标签>,与js混合用
- 字符串字面量用””,表达式{}
- 示例:
<div>示例,{title},{fn()}</div>
- JSX防止注入攻击
- JSX转译为React.createElement()函数调用
元素渲染
- 元素是构成 React 应用的最小砖块
- React元素是不可变元素
组件 & props
函数组件
- 只包含一个render方法并且不包含state时使用
1
2
3function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
class组件
1 | class Welcome extends React.Component { |
- 函数组件与class组件在React中是等效
- 渲染组件(用户自定义组件)–组件名称必须大写
- 组合组件: 在一个组件中使用组件
- 提取组件: 拆分代码,形成组合组件套娃模式
State
- state被称为局部或封装的原因:
- 除了拥有并设置了它的组件,其他组件都无法访问和知道某个组件是什么状态
- ❌不能直接修改state
- √ 使用this.setState({}),构造函数是唯一可以给state赋值的
- ❌this.props 和 this.state 可能会异步更新
- √ 让setState()接收一个函数而不是一个对象
- this.setState((state, props) => {counter: state.counter + props.increment});
- state的更新会被合并:使用setState会浅合并
- 数据是向下流动的:
- 组件可以选择把state作为props向下传递给它的子组件,但子组件不知道它来自的是父组件的state还是props,还是手动输入,这通常被叫做”自上而下”或是”单向”的数据流.
浅合并 & 深合并
- 浅合并:有多层嵌套的情况下,第一层合并且替换,第二层及以下直接替换不合并
- 深合并:有多层嵌套的情况下,每一层合并且替换
事件处理
与DOM 元素事件处理不同处
- 命名采用小驼峰式(aaaBaa)
- 传入一个函数作为事件处理函数 onClick={ 函数名 }
- 不能通过返回false的方式阻止默认行为必须显式使用preventDefault
1
2
3
4
5
6
7
8
9function ActionLink() {
function handleClick(e) {
e.preventDefault(); // 阻止默认事件
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>Click me</a>
);
} - 像onClick={this.handleClick}这种形式,需要绑定this,否则this为undefined
- 解决方案:
- 💚使用箭头函数 onClick={() => this.handleClick()}
- 在constructor中添加 this.handleClick = this.handleClick.bind(this);
- 使用实验性的class fields语法handleClick = () => {console.log(‘this is:’, this);}
- 解决方案:
- 监听器:初始渲染时添加
向事件处理程序传递参数
- 显式:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
- 隐式:
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
条件渲染 (if 元素变量 || && || 三目运算符)
1 | <div> |
阻止组件的渲染(隐藏组件) — 在想要隐藏的组件中判断,直接 return null
列表 & Key
渲染多个组件
1 | const listItems = numbers.map((number) => |
基础列表组件
- 将上面的方法改成一个组件,直接调用该组件
- key – 唯一标识
- key在数组的上下文中被指定,只是兄弟节点之间必须唯一(两个不同数组可以使用相同key)
- key会传递给React,不会传递给组件.需要key属性的值,要用其它属性名显式传递
- JSX允许大括号中前任任何表达式,可以内联map()返回的结果
受控组件 | 非受控组件
- 受控组件:将状态变化交由React处理的组件,通过props获取其当前值,并通过回调函数通知变化
- 非受控组件:通过ref引用获取的组件,使用DOM节点处理(ref)
表单(受控组件)
- 表单状态发生变化时,都会通知React,通过使用 setState()来更新
- textarea 标签(多行文本控件) — input用法相同
- select标签(下拉列表标签)
- 单选:
<select value={this.state.value}>
通过value来设置默认选中 - 多选:
<select multiple={true} value={['B', 'C']}>
- 单选:
- input标签
- input的变化没有更新节点setState,而它的变化是本身具有的功能,所以为非受控组件
- 处理多个input 添加name属性 根据event.target.name 的值选择要执行的操作
- 计算属性名称更新给定输入名称对应的state
- this.setState({ [name]: value });
- 受控输入空值,可能将value指定为undefined或null
状态提升
- 将组件的共享状态提升到最近的共同父组件
- 把子组件的state数据放在它们共同的父组件中保存,然后父组件可以通过props将状态数据传给子组件中
- 组件通过this.props.xxx来接收
组合 vs 继承
- 包含关系 — 一个组件包含在另一个组件中
- 特例关系 — 组合组件,定制渲染
- 现在有一个组件,这个组件又包含在另一个组件,并且值都时确定好的不更改的
- 组件可以直接引入(import)而无需通过(extend)继承它们
生命周期
挂载:当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
更新:当组件的props或state发生变化时会触发更新。其生命周期调用顺序如下:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
卸载:当组件从 DOM 中移除时会调用如下方法:
- componentWillUnmount()
Hook
代码分割
- 引入用import()
- import(“路径”).then(math => { console.log(math.add(10,9));})
Refs 转发(ref:DOM内部)
转发 refs 到 DOM 组件: 允许某些组件接收ref,并将其传递给子组件
1 | const FancyButton = React.forwardRef((props, ref) => ( Ⅲ |
在高阶组件中转发 refs
- 高阶组件ref只能传给外部的组件,内部组件可以通过从props获取forwardRef,在将它传给ref,在返回时就可以挂载到被包裹的子组件上
1
2
3
4
5const {forwardedRef} = this.props
return <Component ref= {forwardRef} />
return React.forwardRef((props,ref) => {
return <外部组件 {...props} forwardedRef={ref} />
});
高阶组件(HOC高阶组件是参数为组件,返回值为新组件的函数)
- 场景: 多个组件对同一个订阅和取消订阅的频繁调用
- 使用withSubscription(被包装组件,(订阅的组件,props)=>返回需要的数据)
- 将不相关的props传递给被包裹的组件
- refs不会被传递,render中不要使用HOC,静态方法也要复制(使用 hoist-non-react-statics 自动拷贝所有非 React 静态方法)
装饰器decorator
- 写完一个函数来封装一个类或其它,然后通过在这个类的上一行用@函数名来使用,这个函数可以通过参数来向这个类的原型上的构造函数添加新的属性
1
2
3
4
5
6const testDecorator = () => {
return (target: any) => {
target.isTeatable =true; //往DecoratorTest原型上的constructor添加了isTeatable =true
console.log(target);
}
}
render props
- 不把一个组件强加在另一个组件内,而是在另一个组件被使用的标签上使用render props
- 模式 :render或其它名字={变量 => (<组件名 变量名={变量} />)}
- 作用:告知组件需要渲染什么内容的函数 prop(大多为高阶组件)
Fragments
- 将子列表分组,可返回多个元素
- 形式: <React.Fragment /> 或 < />
- 作用:像vue中的template一样,在html中不影响布局
PropTypes 进行类型检查
- 限制单个元素 PropTypes.element
- 默认值 defaultProps
- 任意类型数据 requiredAny: PropTypes.any.isRequired,
- 枚举类型 optionalEnum: PropTypes.oneOf([‘News’, ‘Photos’]),
Web Components (第三方UI组件)
- 使用的是class