React Native/React Native_study

[리액트 네이티브를 다루는 기술 #5] 4장 할일 목록 만들기2 (p.161 ~ 178)

bocoder
728x90
반응형

@ List

* 4.1 불변성을 지키면서 객체와 배열 업데이트하기

** 4.1.1 불변성을 지켜야 하는 이유

** 4.1.2 배열의 불변성을 지키는 방법

* 4.2 todos 상태 만들기 및 FlatList로 항목 화면에 나타내기

** 4.2.1 todos 상태 만들기

** 4.2.2 TodoList 컴포넌트 만들기

** 4.2.3 TodoItem 컴포넌트 만들기

** 4.2.4 항목 사이에 구분선 보여주기

** 4.2.5 완료한 항목에 다른 스타일 적용하기

 

@ Note

1. 렌더링 성능 최적화 방식이기 때분에 불변성 지킬 것

 - 항목 추가 시 : spread 연산자, concat()
  > const numbers = [0, 1, 2, 3];
     const nextnumbers = [...number, 4]; // [0, 1, 2, 3, 4]
     const nextnumbers = numbers.concat(4); // [0, 1, 2, 3, 4]

 - 항목 제거 시 : filter()
  > const numbers = [-3, -2, -1, 0, 1, 2, 3];
     const filtered = numbers.filter(number => number > 0); // [1, 2, 3]
     const nextNumbers = numbers.filter(number => number !== 0); // [-3, -2, -1, 1, 2, 3]
     const nextNumbers = numbers.filter((number, i) => i !== 3); // [-3, -2, -1, 1, 2, 3]

 - 항목 수정 시 : map()
  > const numbers = [-3, -2, -1, 0, 1, 2, 3];
     const nextNumbers = numbers.map(number => number === 0 ? 10 : number); // [-3, -2, -1, 10, 1, 2, 3]

 

2. style 적용 시 배열로 여러개 적용 가능

 - 배열 순서에 영향을 받으나, 'done &&' 조건으로 선택적 적용 가능
 - ex) <View style={[styles.circle, done && styles.filled]}>

@ Result

012
ios / android - todos 항목 나타내기 및 컴포넌트 만들기

 

# Source tree

 

# App.js

import React, {useState} from 'react';
import {StyleSheet, KeyboardAvoidingView, Platform} from 'react-native';

import DateHead from './components/DateHead';
// for IOS StatusBar
import {SafeAreaProvider, SafeAreaView} from 'react-native-safe-area-context';

import AddTodo from './components/AddTodo';
import Empty from './components/Empty';
import TodoList from './components/TodoList';

function App() {
  const today = new Date();

  const [todos, setTodos] = useState([
    {id: 1, text: '작업환경 설정', done: true},
    {id: 2, text: '리액트 네이티브 기초 공부', done: false},
    {id: 3, text: '투두리스트 만들어보기', done: false},
  ]);

  return (
    <SafeAreaProvider>
      <SafeAreaView edges={['bottom']} style={styles.block}>
        <KeyboardAvoidingView
          // behavior={Platform.OS === 'ios' ? 'padding' : undefined}  // 삼항연산자 이용 시
          behavior={Platform.select({ios: 'padding', android: undefined})}
          style={styles.avoid}>
          <DateHead date={today} />
          {todos.length === 0 ? <Empty /> : <TodoList todos={todos} />}
          <AddTodo />
        </KeyboardAvoidingView>
      </SafeAreaView>
    </SafeAreaProvider>
  );
}

const styles = StyleSheet.create({
  block: {
    flex: 1,
    backgroundColor: 'white',
  },
  avoid: {
    flex: 1,
  },
});

export default App;

 

# components > AddTodo.js

import React, {useState} from 'react';
import {
  View,
  StyleSheet,
  TextInput,
  Image,
  Platform,
  TouchableOpacity,
  TouchableNativeFeedback,
  Keyboard,
} from 'react-native';

function AddTodo() {
  const [text, setText] = useState('');

  const onPress = () => {
    setText('');
    Keyboard.dismiss(); // button 눌렀을 시 키보드 사라짐
  };

  const button = (
    <View style={styles.buttonStyle}>
      <Image source={require('../assets/icons/add_white/add_white.png')} />
    </View>
  );

  return (
    <View style={styles.block}>
      <TextInput
        placeholder="할일을 입력하세요."
        style={styles.input}
        value={text}
        onChangeText={setText}
        onSubmitEditing={onPress} // Enter 눌렀을 시 키보드 사라짐
        returnKeyType="done" // Enter 타입 지정
      />

      {/* 삼항연산자 이용 */}
      {/* {Platform.OS === 'ios' ? (
        // ios : 터치 시 투명도 변경
        <TouchableOpacity activeOpacity={0.5}>
          <View style={styles.buttonStyle}>
            <Image
              source={require('../assets/icons/add_white/add_white.png')}
            />
          </View>
        </TouchableOpacity>
      ) : (
        // android : 터치 시 물결
        <View style={styles.circleWrapper}>
          <TouchableNativeFeedback>
            <View style={styles.buttonStyle}>
              <Image
                source={require('../assets/icons/add_white/add_white.png')}
              />
            </View>
          </TouchableNativeFeedback>
        </View>
      )} */}

      {Platform.select({
        ios: <TouchableOpacity onPress={onPress}>{button}</TouchableOpacity>,
        android: (
          <View style={styles.circleWrapper}>
            <TouchableNativeFeedback onPress={onPress}>
              {button}
            </TouchableNativeFeedback>
          </View>
        ),
      })}
    </View>
  );
}

const styles = StyleSheet.create({
  block: {
    backgroundcolor: 'white',
    height: 64,
    paddingHorizontal: 16, // 좌우 여백
    bordercolor: '#bdbdbd',
    borderTopWidth: 1,
    borderBottomWidth: 1,
    alignItems: 'center', //상하 정렬
    flexDirection: 'row',
  },
  input: {
    flex: 1, // TextInput 란 확장
    fontSize: 16,
    paddingVertical: 8, // 상하 터치영역 확장
  },
  buttonStyle: {
    alignItems: 'center',
    justifyContent: 'center',
    width: 48,
    height: 48,
    backgroundColor: '#26a69a',
    borderRadius: 24,
  },
  circleWrapper: {
    overflow: 'hidden', // 지정한 영역 외 바깥 영역 숨김
    borderRadius: 24,
  },
});

export default AddTodo;

 

# components > DataHead.js

import React from 'react';
import {View, Text, StyleSheet, StatusBar} from 'react-native';

// for IOS StatusBar
import {useSafeAreaInsets} from 'react-native-safe-area-context';

function DateHead() {
  const d = new Date();
  const year = d.getFullYear();
  const month = d.getMonth() + 1; // getMonth 범위 : 0 ~ 11 까지
  const day = d.getDate();

  const formatted = `${year}년 ${month}월 ${day}일`;

  const {top} = useSafeAreaInsets();

  return (
    <>
      <View style={[styles.statusBarPlaceholder, {height: top}]} />
      <StatusBar backgroundColor="#26a69a" barStyle="light-content" />
      <View style={styles.block}>
        {/* <Text style={styles.dateText}>{formatted}</Text> */}
        <Text style={styles.dateText}>
          {year}년 {month}월 {day}일
        </Text>
      </View>
    </>
  );
}

const styles = StyleSheet.create({
  statusBarPlaceholder: {
    backgroundColor: '#26a69a',
  },
  block: {
    padding: 16,
    backgroundColor: '#26a69a',
  },
  dateText: {
    fontSize: 24,
    color: 'white',
  },
});

export default DateHead;

 

# components > Empty.js

import React from 'react';
import {View, Text, Image, StyleSheet} from 'react-native';

function Empty() {
  //   const source = {uri: 'https://via.placeholder.com/150'};

  return (
    <View style={styles.block}>
      {/* <Image
        source={require('../assets/images/circle.png')}
        style={styles.image}
        resizeMode="cover"
      /> */}
      {/* <Image source={source} style={styles.image} resizeMode="cover" /> */}

      <Image
        source={require('../assets/images/young_and_happy.png')}
        style={styles.image}
        resizeMode="cover"
      />

      <Text style={styles.description}>할일이 없습니다.</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  block: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  image: {
    width: 240,
    height: 179,
    marginBottom: 16,
  },
  description: {
    fontSize: 24,
    color: '#9e9e9e',
  },
});

export default Empty;

 

# components > TodoItem.js

import React from 'react';
import {View, Text, StyleSheet, Image} from 'react-native';

function TodoItem({id, text, done}) {
  return (
    <View style={styles.item}>
      {/* <View style={styles.circle} />
      <Text style={styles.text}>{text}</Text> */}

      <View style={[styles.circle, done && styles.filled]}>
        {done && (
          <Image
            source={require('../assets/icons/check_white/check_white.png')}
          />
        )}
      </View>
      <Text style={[styles.text, done && styles.lineThrough]}>{text}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  item: {
    flexDirection: 'row',
    padding: 16,
    alignItems: 'center',
  },
  circle: {
    width: 24,
    height: 24,
    borderRadius: 12,
    borderWidth: 1,
    borderColor: '#26a69a',
    marginRight: 16,
  },
  filled: {
    alignItems: 'center',
    backgroundColor: '#26a69a',
  },
  text: {
    flex: 1,
    fontSize: 16,
    color: '#212121',
  },
  lineThrough: {
    color: '#9e9e9e',
    textDecorationLine: 'line-through',
  },
});

export default TodoItem;

 

# components > TodoList.js

import React from 'react';
import {FlatList, View, Text, StyleSheet} from 'react-native';
import TodoItem from './TodoItem';

function TodoList({todos}) {
  return (
    <FlatList
      ItemSeparatorComponent={() => <View style={styles.separator} />}
      style={styles.list}
      data={todos}
      renderItem={({item}) => (
        // <View>
        //   <Text>{item.text}</Text>
        // </View>
        <TodoItem id={item.id} text={item.text} done={item.done} />
      )}
      keyExtractor={item => item.id.toString()}
    />
  );
}

const styles = StyleSheet.create({
  list: {
    flex: 1,
  },
  separator: {
    backgroundColor: '#e0e0e0',
    height: 1,
  },
});

export default TodoList;

 

728x90
반응형