组件是 React 代码复用的主要单元,但如何将一个组件封装的状态或行为共享给其他需要相同状态的组件并不是显而易见的。
ReactNative 和 React 一样可以使用函数式组件或 Class 组件。最开始只有 Class 组件能够使用 state ,函数式组件都是无状态的。并且渲染结果只与参数有关,参数相同,每次渲染结果都相同。
组件之间如果有复用的需求,有一些可复用的逻辑需要从组件中抽取出来,通常是使用 render props 或 高阶组件。
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移动鼠标!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
提供了一个 render 方法 让 能够动态决定什么需要渲染,而不是克隆 组件然后硬编码来解决特定的用例。
更具体地说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
高阶组件是参数为组件,返回值为新组件的函数。
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: selectData(DataSource, props)
};
}
// 一些通用的逻辑处理
render() {
// ... 并使用新数据渲染被包装的组件!
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
这两种方案就是16.8版本之前的react所支持的组件间逻辑复用方案了,但是都有缺点,多逻辑复用容易出现嵌套问题。
React Native 0.59 提供Hooks 的新特性,自从有了 React Hooks API,可以在不编写 class 的情况下以类组件的方式使用 state 以及其他的 React 特性
function Example() {
// 声明一个叫 “count” 的 state 变量。
const [count, setCount] = useState(0);
const { x, y } = useMouse();
return (
<View>
<Text>You clicked {count} times</Text>
<Button onClick={() => setCount(count + 1)}>
Click me
</Button>
</View>
);
}
可以利用 Hooks,将 ReactNative 组件打造成:任何事物的变化都是输入源,当这些源变化时会重新触发 ReactNative 组件的 render,你只需要挑选组件绑定哪些数据源(use 哪些 Hooks),然后只管写 render 函数就行了!
与函数组件相比,类组件缺点:
1.只在最顶层使用 Hook
2.只在 React 函数中调用 Hook
App中可以使用 eslint-plugin-react-hooks
的 ESLint 插件来强制执行这两条规则。
useState
useEffect
useContext
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
1.useState
useState让开发者能够在函数组件里面拥有state,修改state。
const [state, setState] = useState(initialState);
2.useEffect
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
3.useRef
可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式,返回的 ref 对象在组件的整个生命周期内持续存在。
与useState区别:变更 .current 属性不会引发组件重新渲染,而useState值的更新会触发组件重新渲染
Redux Hooks API
const dispatch = useDispatch()
const loadingEffect = useSelector(state =>state.loading)
React Navigation
useNavigation
useRoute
useNavigationState
useFocusEffect
...
ReactNative 或者 React 可以通过减少非必要的 render 来优化性能,在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdate
和 PureComponent
而使用函数组件时,除了需要减少非必要render之外,每次 render 都会重新从头开始执行函数,因此还需要注意减少重复的复杂计算或逻辑处理
1.React.memo
这个 API 可以说是对标类组件里面的 PureComponent,这是可以减少重新 render 的次数的。
function Child(props) {
return <Text>{props.name}</Text>
}
export default React.memo(Child)
把声明的组件通过React.memo包一层就好了,React.memo其实是一个高阶函数,传递一个组件进去,返回一个可以记忆的组件。
默认情况下其只会对 props 的复杂对象做浅比较,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
2.useCallback
这个api也可以减少重新 render 的次数
function App() {
const [title, setTitle] = useState("这是一个 title");
const callback = () => {
setTitle("标题改变了");
};
return (
<View>
<Text>{title}</Text>
<Child onClick={callback} name="桃桃" />
</View>
);
}
在函数式组件里每次重新渲染,函数组件都会重头开始重新执行,那么这两次创建的 callback 函数肯定发生了改变,所以导致了子组件重新渲染。
// 通过 useCallback 进行记忆 callback,并将记忆的 callback 传递给 Child
const memoizedCallback = useCallback(()=>{setTitle("标题改变了")}, [])
return (
<View>
<Text>{title}</Text>
<Child onClick={memoizedCallback} name="桃桃" />
</View>
);
}
3.useMemo
useMemo 的使用场景主要是用来缓存计算量比较大的函数结果,可以避免不必要的重复计算
function App() {
const [count, setCount] = useState(1);
function expensiveFn() {
let result = 0;
for (let i = 0; i < 10000; i++) {
result += i;
}
return result;
}
const result = useMemo(expensiveFn, []);
//const result = expensiveFn();
return (
<View >
<Text>count:{count}</Text>
<Text>result:{result}</Text>
<Button onClick={() => setCount(count + 1)}>count+1</Button>
</View>
);
}
除了React Hooks提供的基础hook,业务开发中还会有许多常用的基础功能或者可以复用的逻辑,这些也可以抽离成自定义的hooks来实现复用。
Class组件
class CrmCreat extends Component {
constructor(props) {
super(props)
this.state = { }
}
componentDidMount() {}
addItem = () => { }
showRegionDialog = () => { }
showShadow = () => { }
hideShadow = () => { }
disable = () => {}
submit = () => { }
check() {}
checkCompanyName() { }
searchCompany = name => {}
renderModal = () => {}
render() { }
}
使用Hooks封装的函数组件
export default props => {
const { navigation } = props
const { qccQuery, crmEdit, crmAdd, checkName } = ajaxStore.crm
const modal = useRef(null)
const [state, setState] = useMergeState({})
const { show, shadow } = useRegionDialog({})
const { doFetch: _qccQuery } = useAsync({})
const { doFetch: _crmEdit } = useAsync({})
const { doFetch: _crmAdd } = useAsync({})
const { data: isChecked, doFetch: _checkName } = useAsync({})
const timer = useDebounce(name => {}, 500)
const { check } = useFormValid({})
const addItem = () => { }
const checkPerson = () => { }
const checkCompanyName = async () => {}
const renderModal = () => {}
return (<View></View>)
}