[React native / 리액트 네이티브] Grid 구현 방법,  FlatList, numColumns, nativeEvent
Code/React-Native

[React native / 리액트 네이티브] Grid 구현 방법, FlatList, numColumns, nativeEvent

반응형

완성 예제

자사 서비스는 웹을 기반으로 작업이 되어있었다. 그 후 어플을 런칭하면서 서비스 확장을 기대했고 1차적으로 webVeiw를 통해 안드로이드, ios 에서 볼 수 있도록 리액트 네이티브를 사용했었는데 webView로는 ux적인 경험이 부드럽지 못하다는 내부 결론이 도출되었고, 리액트 네이티브 (react-native)로 서비스를 모두 옮기는 작업을 진행하고 있는 단계이다.

완전히 몰두해서 일하기까지는 일주일 정도의 적응기를 가졌는데, 네이티브를 셋팅하는 데 너무 많은 변수 + xcode의 버릇없음 + 맥북의 대환장 먹통 파티에 3일정도를 썼고, 나머지 이틀은 ui가 복잡하지않은 페이지들을 작업하면서 공식문서를 계속 읽어가면서 작업했다.

그렇게 간단한 페이지 작업을 끝낸 후 grid 형태의 디자인을 구현했어야 했는데(서비스 페이지 상당 부분이 모두 grid 형태), react native에서는 grid css를 따로 지원하지 않고 있었고, 이런 저런 싸움을 하며 구글에서 파도타며 돌아다니다가 나름의 grid 구현 방법을 찾아서 이를 포스팅하고자 했다. 

혹시라도 네이티브에서 grid 형태의 디자인을 구현해야 하는데 막혀있다면... 각 아이템에 마진과 패딩을 설정해야 하는데 인터넷에 내가 바라는 느낌의 정답이 없는 것 같다면 부디 내 글이.. 헤엄치는 그 누군가에게 도움이 되었으면 한다.  

예제를 위한 디자인 파일이라 필체는 이해하실 거라 믿어 의심치 않습니다..

체크 사항

1. 아이템간의 여백을 확인해야 함 

2. 전체 화면 가로 사이즈에서 양옆 패딩이 33px씩 차지한다

3. 디바이스의 환경에 따라 반응형으로 아이템의 가로도 변경되어야 함.

 

FlatList

네이티브에선 리스트 형식을 렌더링할 때 사용하는 컴포넌트들이 있는데 그 중 FlatList를 설명하려고 한다. 

 

const DATA = [
  {
    title: 1,
  },
  {
    title: 2,
  },
  {
    title: 3,
  },
  {
    title: 4,
  },
  {
    title: 5,
  },
  {
    title: 6,
  },
  {
    title: 7,
  },
];

const Item = ({title}) => (
  <View
    style={{
      backgroundColor: '#f9c2ff',
      padding: 20,
      marginVertical: 8,
      marginHorizontal: 16,
    }}>
    <Text style={{color: 'black', fontSize: 14}}>{title}</Text>
  </View>
);

const App = () => {

  return (
    <SafeAreaView style={{marginTop: 0}}>
      <View style={{paddingHorizontal: 33}}>
        <FlatList
          data={DATA}
          renderItem={({item}) => <Item title={item.title} />}
          keyExtractor={(item, index) => index}
        />
      </View>
    </SafeAreaView>
  );
};

 

위 코드에서 스타일은 배경 색 + 패딩값만 부여했기때문에 확인하지 않아도 되고, FlatList의 기본 사용법만 이해하면 된다.

FlatList의 data 프롭스에는 배열 형식의, 쉽게 말해 맵핑하고싶은 데이터를 넣으면 된다. 

renderItem 프롭스에서는 data 프롭스에서 받은 데이터를 활용할 틀을 만드는 곳이다. item의 값은 DATA[i] 인 것이다.

item 데이터를 Item 컴포넌트에 프롭스로 전달하고 FlatList는 열심히 만들어 내면 위 스샷과 같이 구현된다.

그럼 여기서 이제 flex를 활용해서 만들면 되는 것 아닌가?라고 생각한다면 전혀 아니다. (단호)

 

numColums

      <FlatList
        data={DATA}
        renderItem={({item}) => <Item title={item.title} />}
        keyExtractor={(item, index) => index}
        numColumns={3}
      />

numColums 를 활용할 수 있다. 그럼 칼럼 3개의 형태를 유지하게되는데 이제 문제는 width이다. 

아이템 컨테이너의 여백을 모두 제거한 값을 동일하게 칼럼수만큼 나눈 게 width가 되어야 한다. 이런 경우 사용할 수 있는 게 onLayOut이다.

 

onLayout

const Item = ({title, width}) => (
  <View
    style={{
      width,
      backgroundColor: '#f9c2ff',
      padding: 20,
    }}>
    <Text style={{color: 'black', fontSize: 14}}>{title}</Text>
  </View>
);

const App = () => {
  const [containerWidth, setContainerWidth] = useState(0);

  const margins = 39 * 2;
  const numColumns = 3;

  return (
    <SafeAreaView style={{marginTop: 0}}>
      <View style={{paddingHorizontal: 33}}>
        <FlatList
          data={DATA}
          onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}
          renderItem={({item}) => (
            <Item
              title={item.title}
              width={(containerWidth - margins) / numColumns}
            />
          )}
          keyExtractor={(item, index) => index}
          numColumns={numColumns}
        />
      </View>
    </SafeAreaView>
  );
};

onLayout 을 통해서 레이아웃이 만들어졌을 때, 현재 요소가 가지고 있는 width 크기를 가져올 수 있는데 그걸 상태로 저장한다.

그 후 순수한 단일 아이템의 width를 만들기 위해선 해당 containerWidth에서 모든 여백의 값들을 제거하고, 칼럼수만큼 나눠주면 된다.

 

++ 2023.01.05 내용 추가

onLayout이 렌더링또는 레이아웃 변경 시 실행되지만 환경에 따라 값을 받아오는 데에 일부 딜레이가 발생하여 초기에 원하는 값을 받지 못하는 경우가 있었습니다.

const windowWidth = Dimensions.get('window').width;
const windowHeight = Dimensions.get('window').height;

https://reactnative.dev/docs/dimensions

react-native의 Dimensions 객체를 사용해 디바이스의 width와 height을 받고 아이템 갯수로 나누어 width또는 height를 스타일에 설정하는 것이 가장 좋은 방법이었습니다.

 

Dimensions · React Native

useWindowDimensions is the preferred API for React components. Unlike Dimensions, it updates as the window's dimensions update. This works nicely with the React paradigm.

reactnative.dev

반응형

columnWrapperStyle

const App = () => {
  const [containerWidth, setContainerWidth] = useState(0);

  const margins = 39 * 2;
  const numColumns = 3;

  return (
    <SafeAreaView style={{marginTop: 0}}>
      <View style={{paddingHorizontal: 33}}>
        <FlatList
          data={DATA}
          columnWrapperStyle={{
            justifyContent: 'space-between',
            marginBottom: 32,
          }}
          onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}
          renderItem={({item}) => (
            <Item
              title={item.title}
              width={(containerWidth - margins) / numColumns}
            />
          )}
          keyExtractor={(item, index) => index}
          numColumns={numColumns}
        />
      </View>
    </SafeAreaView>
  );
};

 

columnWrapperStyle 프롭스는 한 줄에 대한 css를 설정할 수 있다. 이곳에서 space-between으로 쫘악쫘악 밀어주고 마진값을 부여하면 원하던 그리드가 완성된다.

 

이외에도 FlatList를 사용하면서 여러 에러나 핸들링이 필요해서 포스팅을 생각 중이지만 오늘은 기본 grid만 포스팅하려고 한다.

 

 

 

반응형