mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-31 09:44:21 +00:00
[change] modernize View
Rewrite View to use function components and hooks. The 'usePlatformMethods' hook also fixes a bug in the class-based implementation of 'setNativeProps' which was unable to correctly merge its styles with those provided via the component API. In the future, 'setNativeProps' will be removed from React Native anyway. See (3) in #1136 for more context.
This commit is contained in:
@@ -7,6 +7,9 @@
|
|||||||
* @flow strict
|
* @flow strict
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import type { Context } from 'react';
|
||||||
const TextAncestorContext = React.createContext(false);
|
|
||||||
export default (TextAncestorContext: React.Context<boolean>);
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
const TextAncestorContext = createContext(false);
|
||||||
|
export default (TextAncestorContext: Context<boolean>);
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ describe('components/View', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('prop "hitSlop"', () => {
|
describe('prop "hitSlop"', () => {
|
||||||
it('renders a span with negative position offsets', () => {
|
test('renders a span with negative position offsets', () => {
|
||||||
const { container } = render(<View hitSlop={{ top: 10, bottom: 10, right: 5, left: 5 }} />);
|
const { container } = render(<View hitSlop={{ top: 10, bottom: 10, right: 5, left: 5 }} />);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles partial offsets', () => {
|
test('handles partial offsets', () => {
|
||||||
const { container } = render(<View hitSlop={{ top: 10 }} />);
|
const { container } = render(<View hitSlop={{ top: 10 }} />);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|||||||
+53
-53
@@ -10,73 +10,73 @@
|
|||||||
|
|
||||||
import type { ViewProps } from './types';
|
import type { ViewProps } from './types';
|
||||||
|
|
||||||
import applyLayout from '../../modules/applyLayout';
|
|
||||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
|
||||||
import createElement from '../createElement';
|
import createElement from '../createElement';
|
||||||
import css from '../StyleSheet/css';
|
import css from '../StyleSheet/css';
|
||||||
import filterSupportedProps from './filterSupportedProps';
|
import filterSupportedProps from './filterSupportedProps';
|
||||||
|
import setAndForwardRef from '../../modules/setAndForwardRef';
|
||||||
|
import useElementLayout from '../../hooks/useElementLayout';
|
||||||
|
import usePlatformMethods from '../../hooks/usePlatformMethods';
|
||||||
import StyleSheet from '../StyleSheet';
|
import StyleSheet from '../StyleSheet';
|
||||||
import TextAncestorContext from '../Text/TextAncestorContext';
|
import TextAncestorContext from '../Text/TextAncestorContext';
|
||||||
import React from 'react';
|
import React, { forwardRef, useContext, useRef } from 'react';
|
||||||
|
|
||||||
export type { ViewProps };
|
export type { ViewProps };
|
||||||
|
|
||||||
const calculateHitSlopStyle = hitSlop => {
|
function createHitSlopElement(hitSlop) {
|
||||||
const hitStyle = {};
|
const hitSlopStyle = {};
|
||||||
for (const prop in hitSlop) {
|
for (const prop in hitSlop) {
|
||||||
if (hitSlop.hasOwnProperty(prop)) {
|
if (hitSlop.hasOwnProperty(prop)) {
|
||||||
const value = hitSlop[prop];
|
const value = hitSlop[prop];
|
||||||
hitStyle[prop] = value > 0 ? -1 * value : 0;
|
hitSlopStyle[prop] = value > 0 ? -1 * value : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hitStyle;
|
return createElement('span', {
|
||||||
};
|
classList: [classes.hitSlop],
|
||||||
|
style: hitSlopStyle
|
||||||
class View extends React.Component<ViewProps> {
|
});
|
||||||
static displayName = 'View';
|
|
||||||
|
|
||||||
renderView(hasTextAncestor) {
|
|
||||||
const hitSlop = this.props.hitSlop;
|
|
||||||
const supportedProps = filterSupportedProps(this.props);
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
React.Children.toArray(this.props.children).forEach(item => {
|
|
||||||
if (typeof item === 'string') {
|
|
||||||
console.error(
|
|
||||||
`Unexpected text node: ${item}. A text node cannot be a child of a <View>.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
supportedProps.classList = [classes.view];
|
|
||||||
supportedProps.ref = this.props.forwardedRef;
|
|
||||||
supportedProps.style = StyleSheet.compose(
|
|
||||||
hasTextAncestor && styles.inline,
|
|
||||||
this.props.style
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hitSlop) {
|
|
||||||
const hitSlopStyle = calculateHitSlopStyle(hitSlop);
|
|
||||||
const hitSlopChild = createElement('span', {
|
|
||||||
classList: [classes.hitSlop],
|
|
||||||
style: hitSlopStyle
|
|
||||||
});
|
|
||||||
supportedProps.children = React.Children.toArray([hitSlopChild, supportedProps.children]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return createElement('div', supportedProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<TextAncestorContext.Consumer>
|
|
||||||
{hasTextAncestor => this.renderView(hasTextAncestor)}
|
|
||||||
</TextAncestorContext.Consumer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const View = forwardRef<ViewProps, *>((props, ref) => {
|
||||||
|
const { forwardedRef, hitSlop, onLayout, style, ...rest } = props;
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
React.Children.toArray(props.children).forEach(item => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
console.error(`Unexpected text node: ${item}. A text node cannot be a child of a <View>.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const classList = [classes.view];
|
||||||
|
const hasTextAncestor = useContext(TextAncestorContext);
|
||||||
|
const hostRef = useRef(null);
|
||||||
|
|
||||||
|
const setRef = setAndForwardRef({
|
||||||
|
getForwardedRef: () => forwardedRef,
|
||||||
|
setLocalRef: c => {
|
||||||
|
hostRef.current = c;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useElementLayout(hostRef, onLayout);
|
||||||
|
usePlatformMethods(hostRef, ref, classList, style);
|
||||||
|
|
||||||
|
const supportedProps = filterSupportedProps(rest);
|
||||||
|
supportedProps.children = hitSlop
|
||||||
|
? React.Children.toArray([createHitSlopElement(hitSlop), props.children])
|
||||||
|
: props.children;
|
||||||
|
supportedProps.classList = classList;
|
||||||
|
supportedProps.ref = setRef;
|
||||||
|
supportedProps.style = StyleSheet.compose(
|
||||||
|
hasTextAncestor && styles.inline,
|
||||||
|
style
|
||||||
|
);
|
||||||
|
|
||||||
|
return createElement('div', supportedProps);
|
||||||
|
});
|
||||||
|
|
||||||
|
View.displayName = 'View';
|
||||||
|
|
||||||
const classes = css.create({
|
const classes = css.create({
|
||||||
view: {
|
view: {
|
||||||
alignItems: 'stretch',
|
alignItems: 'stretch',
|
||||||
@@ -111,4 +111,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default applyLayout(applyNativeMethods(View));
|
export default View;
|
||||||
|
|||||||
Reference in New Issue
Block a user